Enable parsing of simple let-expressions
This commit is contained in:
commit
d813e85d00
11 changed files with 2243 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
/lib/
|
||||||
|
Makefile
|
391
package-lock.json
generated
Normal file
391
package-lock.json
generated
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
{
|
||||||
|
"name": "@samvv/bolt",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@samvv/bolt",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
|
"yargs": "^17.5.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"bolt": "lib/bin/bolt.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.7.13",
|
||||||
|
"@types/yargs": "^17.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "18.7.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
||||||
|
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/yargs": {
|
||||||
|
"version": "17.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz",
|
||||||
|
"integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/yargs-parser": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/yargs-parser": {
|
||||||
|
"version": "20.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz",
|
||||||
|
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
||||||
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map-support": {
|
||||||
|
"version": "0.5.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "17.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||||
|
"integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "21.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz",
|
||||||
|
"integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "18.7.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
||||||
|
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/yargs": {
|
||||||
|
"version": "17.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz",
|
||||||
|
"integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/yargs-parser": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/yargs-parser": {
|
||||||
|
"version": "20.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz",
|
||||||
|
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||||
|
},
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
||||||
|
},
|
||||||
|
"cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"escalade": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
|
||||||
|
},
|
||||||
|
"get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
|
},
|
||||||
|
"is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
||||||
|
},
|
||||||
|
"require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
|
},
|
||||||
|
"source-map-support": {
|
||||||
|
"version": "0.5.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
|
"requires": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"requires": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||||
|
},
|
||||||
|
"wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||||
|
},
|
||||||
|
"yargs": {
|
||||||
|
"version": "17.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||||
|
"integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==",
|
||||||
|
"requires": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yargs-parser": {
|
||||||
|
"version": "21.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz",
|
||||||
|
"integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
package.json
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "@samvv/bolt",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A new programming language for the web",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"bin": {
|
||||||
|
"bolt": "lib/bin/bolt.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/BoltJS"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"programming-language",
|
||||||
|
"productivity",
|
||||||
|
"development",
|
||||||
|
"performance"
|
||||||
|
],
|
||||||
|
"author": "Sam Vervaeck",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
|
"yargs": "^17.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.7.13",
|
||||||
|
"@types/yargs": "^17.0.11"
|
||||||
|
}
|
||||||
|
}
|
42
src/bin/bolt.ts
Normal file
42
src/bin/bolt.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import "source-map-support/register"
|
||||||
|
|
||||||
|
import path from "path"
|
||||||
|
import fs from "fs"
|
||||||
|
import yargs from "yargs"
|
||||||
|
|
||||||
|
import { Diagnostics } from "../diagnostics"
|
||||||
|
import { Punctuator, Scanner } from "../scanner"
|
||||||
|
import { Parser } from "../parser"
|
||||||
|
|
||||||
|
yargs
|
||||||
|
.string('work-dir')
|
||||||
|
.describe('work-dir', 'Act as if run from this directory')
|
||||||
|
.default('work-dir', '.')
|
||||||
|
.alias('work-dir', 'C')
|
||||||
|
.command(
|
||||||
|
['$0 <file>', 'exec <file>'],
|
||||||
|
'Execute a Bolt script',
|
||||||
|
yargs => yargs
|
||||||
|
.string('file')
|
||||||
|
.demandOption('file')
|
||||||
|
.describe('file', 'Path to the script to execute')
|
||||||
|
, args => {
|
||||||
|
|
||||||
|
const cwd = args.C;
|
||||||
|
const filename = path.resolve(cwd, args.file);
|
||||||
|
const diagnostics = new Diagnostics();
|
||||||
|
const text = fs.readFileSync(filename, 'utf8')
|
||||||
|
const scanner = new Scanner(text, 0, diagnostics);
|
||||||
|
const punctuated = new Punctuator(scanner);
|
||||||
|
const parser = new Parser(punctuated);
|
||||||
|
const sourceFile = parser.parseSourceFile();
|
||||||
|
|
||||||
|
console.log(sourceFile);
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.help()
|
||||||
|
.version()
|
||||||
|
.argv
|
0
src/checker.ts
Normal file
0
src/checker.ts
Normal file
847
src/cst.ts
Normal file
847
src/cst.ts
Normal file
|
@ -0,0 +1,847 @@
|
||||||
|
|
||||||
|
export type TextSpan = [number, number];
|
||||||
|
|
||||||
|
export class TextPosition {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public offset: number,
|
||||||
|
public line: number,
|
||||||
|
public column: number,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public clone(): TextPosition {
|
||||||
|
return new TextPosition(
|
||||||
|
this.offset,
|
||||||
|
this.line,
|
||||||
|
this.column,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public advance(text: string): void {
|
||||||
|
for (const ch of text) {
|
||||||
|
if (ch === '\n') {
|
||||||
|
this.line++;
|
||||||
|
this.column = 1;
|
||||||
|
} else {
|
||||||
|
this.column++;
|
||||||
|
}
|
||||||
|
this.offset += text.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextRange {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public start: TextPosition,
|
||||||
|
public end: TextPosition,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public clone(): TextRange {
|
||||||
|
return new TextRange(
|
||||||
|
this.start.clone(),
|
||||||
|
this.end.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextFile {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public origPath: string,
|
||||||
|
public text: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum SyntaxKind {
|
||||||
|
|
||||||
|
// Tokens
|
||||||
|
Identifier,
|
||||||
|
CustomOperator,
|
||||||
|
LParen,
|
||||||
|
RParen,
|
||||||
|
LBrace,
|
||||||
|
RBrace,
|
||||||
|
LBracket,
|
||||||
|
RBracket,
|
||||||
|
Dot,
|
||||||
|
DotDot,
|
||||||
|
Comma,
|
||||||
|
Colon,
|
||||||
|
Equals,
|
||||||
|
Integer,
|
||||||
|
StringLiteral,
|
||||||
|
LetKeyword,
|
||||||
|
PubKeyword,
|
||||||
|
MutKeyword,
|
||||||
|
ModKeyword,
|
||||||
|
ImportKeyword,
|
||||||
|
StructKeyword,
|
||||||
|
TypeKeyword,
|
||||||
|
LineFoldEnd,
|
||||||
|
BlockEnd,
|
||||||
|
BlockStart,
|
||||||
|
EndOfFile,
|
||||||
|
|
||||||
|
// Type expressions
|
||||||
|
ReferenceTypeExpression,
|
||||||
|
|
||||||
|
// Patterns
|
||||||
|
BindPattern,
|
||||||
|
TuplePattern,
|
||||||
|
|
||||||
|
// Expressions
|
||||||
|
ReferenceExpression,
|
||||||
|
TupleExpression,
|
||||||
|
NestedExpression,
|
||||||
|
ConstantExpression,
|
||||||
|
PrefixExpression,
|
||||||
|
PostfixExpression,
|
||||||
|
InfixExpression,
|
||||||
|
|
||||||
|
// Statements
|
||||||
|
ReturnStatement,
|
||||||
|
ExpressionStatement,
|
||||||
|
|
||||||
|
// Declarations
|
||||||
|
VariableDeclaration,
|
||||||
|
PrefixFuncDecl,
|
||||||
|
SuffixFuncDecl,
|
||||||
|
LetDeclaration,
|
||||||
|
StructDeclaration,
|
||||||
|
ImportDeclaration,
|
||||||
|
TypeAliasDeclaration,
|
||||||
|
|
||||||
|
// Other nodes
|
||||||
|
StructDeclarationField,
|
||||||
|
ExprBody,
|
||||||
|
BlockBody,
|
||||||
|
TypeAssert,
|
||||||
|
Param,
|
||||||
|
Module,
|
||||||
|
SourceFile,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Syntax
|
||||||
|
= SourceFile
|
||||||
|
| Param
|
||||||
|
| StructDeclarationField
|
||||||
|
| Declaration
|
||||||
|
| Statement
|
||||||
|
| Expression
|
||||||
|
| TypeExpression
|
||||||
|
| Pattern
|
||||||
|
|
||||||
|
abstract class SyntaxBase {
|
||||||
|
|
||||||
|
public abstract readonly kind: SyntaxKind;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TokenBase extends SyntaxBase {
|
||||||
|
|
||||||
|
private endPos: TextPosition | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private startPos: TextPosition,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStartPosition(): TextPosition {
|
||||||
|
return this.startPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStartLine(): number {
|
||||||
|
return this.getStartPosition().line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStartColumn(): number {
|
||||||
|
return this.getStartPosition().column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEndPosition(): TextPosition {
|
||||||
|
if (this.endPos === null) {
|
||||||
|
const endPos = this.getStartPosition().clone();
|
||||||
|
endPos.advance(this.text);
|
||||||
|
return this.endPos = endPos;
|
||||||
|
}
|
||||||
|
return this.endPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEndLine(): number {
|
||||||
|
return this.getEndPosition().line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEndColumn(): number {
|
||||||
|
return this.getEndPosition().column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract readonly text: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class VirtualTokenBase extends TokenBase {
|
||||||
|
public get text(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EndOfFile extends VirtualTokenBase {
|
||||||
|
public readonly kind = SyntaxKind.EndOfFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlockEnd extends VirtualTokenBase {
|
||||||
|
public readonly kind = SyntaxKind.BlockEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlockStart extends VirtualTokenBase {
|
||||||
|
public readonly kind = SyntaxKind.BlockStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LineFoldEnd extends VirtualTokenBase {
|
||||||
|
public readonly kind = SyntaxKind.LineFoldEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Integer extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Integer;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public value: bigint,
|
||||||
|
public radix: number,
|
||||||
|
private startPos: TextPosition,
|
||||||
|
) {
|
||||||
|
super(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
switch (this.radix) {
|
||||||
|
case 16:
|
||||||
|
return '0x' + this.value.toString(16);
|
||||||
|
case 10:
|
||||||
|
return this.value.toString(10)
|
||||||
|
case 8:
|
||||||
|
return '0o' + this.value.toString(8)
|
||||||
|
case 2:
|
||||||
|
return '0b' + this.value.toString(2);
|
||||||
|
default:
|
||||||
|
throw new Error(`Radix ${this.radix} of Integer not recognised.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StringLiteral extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.StringLiteral;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public contents: string,
|
||||||
|
private startPos: TextPosition,
|
||||||
|
) {
|
||||||
|
super(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
let out = '"';
|
||||||
|
for (const ch of this.contents) {
|
||||||
|
const code = ch.charCodeAt(0);
|
||||||
|
if (code >= 32 && code <= 127) {
|
||||||
|
out += ch;
|
||||||
|
} else if (code <= 127) {
|
||||||
|
out += '\\x' + code.toString(16).padStart(2, '0');
|
||||||
|
} else {
|
||||||
|
out += '\\u' + code.toString(17).padStart(4, '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += '"';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Identifier extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Identifier;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public text: string,
|
||||||
|
private startPos: TextPosition,
|
||||||
|
) {
|
||||||
|
super(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomOperator extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.CustomOperator;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public text: string,
|
||||||
|
private startPos: TextPosition,
|
||||||
|
) {
|
||||||
|
super(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LParen extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.LParen;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '(';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RParen extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.RParen;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LBrace extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.LBrace;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '{';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RBrace extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.RBrace;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LBracket extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.LBracket;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '[';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RBracket extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.RBracket;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Dot extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Dot;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Comma extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Comma;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DotDot extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.DotDot;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '..';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Colon extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Colon;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Equals extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Equals;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StructKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.StructKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'struct';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ModKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'mod';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MutKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.MutKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'mut';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ImportKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ImportKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'import'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.TypeKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'type';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PubKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.PubKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'pub';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LetKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.LetKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'let';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Token
|
||||||
|
= LParen
|
||||||
|
| RParen
|
||||||
|
| LBrace
|
||||||
|
| RBrace
|
||||||
|
| LBracket
|
||||||
|
| RBracket
|
||||||
|
| Identifier
|
||||||
|
| CustomOperator
|
||||||
|
| Integer
|
||||||
|
| StringLiteral
|
||||||
|
| Comma
|
||||||
|
| Dot
|
||||||
|
| DotDot
|
||||||
|
| Colon
|
||||||
|
| Equals
|
||||||
|
| LetKeyword
|
||||||
|
| PubKeyword
|
||||||
|
| MutKeyword
|
||||||
|
| ModKeyword
|
||||||
|
| ImportKeyword
|
||||||
|
| TypeKeyword
|
||||||
|
| StructKeyword
|
||||||
|
| EndOfFile
|
||||||
|
| BlockStart
|
||||||
|
| BlockEnd
|
||||||
|
| LineFoldEnd
|
||||||
|
|
||||||
|
export type TokenKind
|
||||||
|
= Token['kind']
|
||||||
|
|
||||||
|
export class ReferenceTypeExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ReferenceTypeExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public modulePath: Array<[Identifier, Dot]>,
|
||||||
|
public name: Identifier,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TypeExpression
|
||||||
|
= ReferenceTypeExpression
|
||||||
|
|
||||||
|
export class BindPattern extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.BindPattern;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: Identifier,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isHole(): boolean {
|
||||||
|
return this.name.text == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TuplePattern extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.TuplePattern;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public elements: Pattern[],
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Pattern
|
||||||
|
= BindPattern
|
||||||
|
| TuplePattern
|
||||||
|
|
||||||
|
export class TupleExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.TupleExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public lparen: LParen,
|
||||||
|
public elements: Expression[],
|
||||||
|
public rparen: RParen,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NestedExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.NestedExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public lparen: LParen,
|
||||||
|
public expression: Expression,
|
||||||
|
public rparen: RParen,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConstantExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ConstantExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public token: Integer | StringLiteral,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReferenceExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ReferenceExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public modulePath: Array<[Identifier, Dot]>,
|
||||||
|
public name: Identifier
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrefixExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.PrefixExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public operator: Token,
|
||||||
|
public expression: Expression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostfixExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.PostfixExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public expression: Expression,
|
||||||
|
public operator: Token,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InfixExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.InfixExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public left: Expression,
|
||||||
|
public operator: Token,
|
||||||
|
public right: Expression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Expression
|
||||||
|
= ReferenceExpression
|
||||||
|
| ConstantExpression
|
||||||
|
| TupleExpression
|
||||||
|
| NestedExpression
|
||||||
|
| PrefixExpression
|
||||||
|
| InfixExpression
|
||||||
|
| PostfixExpression
|
||||||
|
|
||||||
|
export class ReturnStatement extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ReturnStatement;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public expr: Expression
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpressionStatement extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ExpressionStatement;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public expresion: Expression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Statement
|
||||||
|
= ReturnStatement
|
||||||
|
| ExpressionStatement
|
||||||
|
|
||||||
|
export class Param extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Param;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public pattern: Pattern,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StructDeclarationField extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.StructDeclarationField;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: Identifier,
|
||||||
|
public colon: Colon,
|
||||||
|
public typeExpr: TypeExpression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StructDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.StructDeclaration;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public structKeyword: StructKeyword,
|
||||||
|
public name: Identifier,
|
||||||
|
public members: StructDeclarationField[] | null,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeAssert extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.TypeAssert;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public colon: Colon,
|
||||||
|
public typeExpression: TypeExpression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Body
|
||||||
|
= ExprBody
|
||||||
|
| BlockBody
|
||||||
|
|
||||||
|
export class ExprBody extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ExprBody;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public equals: Equals,
|
||||||
|
public expression: Expression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LetBodyElement
|
||||||
|
= LetDeclaration
|
||||||
|
| Statement
|
||||||
|
|
||||||
|
export class BlockBody extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.BlockBody;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public blockStart: BlockStart,
|
||||||
|
public elements: LetBodyElement[],
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export class LetDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.LetDeclaration;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public pubKeyword: PubKeyword | null,
|
||||||
|
public letKeyword: LetKeyword,
|
||||||
|
public mutKeyword: MutKeyword | null,
|
||||||
|
public pattern: Pattern,
|
||||||
|
public params: Param[],
|
||||||
|
public typeAssert: TypeAssert | null,
|
||||||
|
public body: Body | null,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ImportDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ImportDeclaration;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public importKeyword: ImportKeyword,
|
||||||
|
public importSource: StringLiteral,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Declaration
|
||||||
|
= LetDeclaration
|
||||||
|
| ImportDeclaration
|
||||||
|
| StructDeclaration
|
||||||
|
|
||||||
|
export class Module extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Module;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public modKeyword: ModKeyword,
|
||||||
|
public name: Identifier,
|
||||||
|
public body: Body,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SourceFileElement
|
||||||
|
= Statement
|
||||||
|
| Declaration
|
||||||
|
| Module
|
||||||
|
|
||||||
|
export class SourceFile extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.SourceFile;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public elements: SourceFileElement[]
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public *getChildNodes(): Iterable<Syntax> {
|
||||||
|
for (const element in this.elements) {
|
||||||
|
yield element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/diagnostics.ts
Normal file
32
src/diagnostics.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
export class UnexpectedCharDiagnostic {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public text: string,
|
||||||
|
public offset: number,
|
||||||
|
public actual: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public format(): string {
|
||||||
|
let out = `error: unexpeced character '${this.actual}'.`;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Diagnostic
|
||||||
|
= UnexpectedCharDiagnostic;
|
||||||
|
|
||||||
|
export class Diagnostics {
|
||||||
|
|
||||||
|
private savedDiagnostics: Diagnostic[] = [];
|
||||||
|
|
||||||
|
public add(diagnostic: Diagnostic): void {
|
||||||
|
this.savedDiagnostics.push(diagnostic);
|
||||||
|
process.stderr.write(diagnostic.format());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
425
src/parser.ts
Normal file
425
src/parser.ts
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
|
||||||
|
import { kMaxLength } from "buffer";
|
||||||
|
import {
|
||||||
|
ReferenceTypeExpression,
|
||||||
|
SourceFile,
|
||||||
|
SourceFileElement,
|
||||||
|
StructDeclaration,
|
||||||
|
StructDeclarationField,
|
||||||
|
SyntaxKind,
|
||||||
|
Token,
|
||||||
|
TokenKind,
|
||||||
|
Expression,
|
||||||
|
TypeExpression,
|
||||||
|
ConstantExpression,
|
||||||
|
ReferenceExpression,
|
||||||
|
Dot,
|
||||||
|
Identifier,
|
||||||
|
TupleExpression,
|
||||||
|
PrefixExpression,
|
||||||
|
ExpressionStatement,
|
||||||
|
ImportDeclaration,
|
||||||
|
FunctionDeclaration,
|
||||||
|
Param,
|
||||||
|
Pattern,
|
||||||
|
BindPattern,
|
||||||
|
LetDeclaration,
|
||||||
|
TypeAssert,
|
||||||
|
ExprBody,
|
||||||
|
BlockBody,
|
||||||
|
} from "./cst"
|
||||||
|
import { Stream, MultiDict } from "./util";
|
||||||
|
|
||||||
|
const DESCRIPTIONS: Record<SyntaxKind, string> = {
|
||||||
|
[SyntaxKind.StringLiteral]: 'a string literal',
|
||||||
|
[SyntaxKind.Identifier]: "an identifier",
|
||||||
|
[SyntaxKind.Comma]: "','",
|
||||||
|
[SyntaxKind.Colon]: "':'",
|
||||||
|
[SyntaxKind.Integer]: "an integer",
|
||||||
|
[SyntaxKind.LParen]: "'('",
|
||||||
|
[SyntaxKind.RParen]: "')'",
|
||||||
|
[SyntaxKind.LBrace]: "'{'",
|
||||||
|
[SyntaxKind.RBrace]: "'}'",
|
||||||
|
[SyntaxKind.LBracket]: "'['",
|
||||||
|
[SyntaxKind.RBracket]: "']'",
|
||||||
|
[SyntaxKind.ConstantExpression]: 'a constant expression',
|
||||||
|
[SyntaxKind.ReferenceExpression]: 'a reference expression',
|
||||||
|
[SyntaxKind.LineFoldEnd]: 'the end of the current line-fold',
|
||||||
|
[SyntaxKind.TupleExpression]: 'a tuple expression such as (1, 2)',
|
||||||
|
[SyntaxKind.ReferenceExpression]: 'a reference to some variable',
|
||||||
|
[SyntaxKind.NestedExpression]: 'an expression nested with parentheses',
|
||||||
|
[SyntaxKind.ConstantExpression]: 'a constant expression such as 1 or "foo"',
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeSyntaxKind(kind: SyntaxKind): string {
|
||||||
|
const desc = DESCRIPTIONS[kind];
|
||||||
|
if (desc === undefined) {
|
||||||
|
throw new Error(`Could not describe SyntaxKind '${kind}'`);
|
||||||
|
}
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeExpected(expected: SyntaxKind[]) {
|
||||||
|
if (expected.length === 0) {
|
||||||
|
return 'nothing';
|
||||||
|
}
|
||||||
|
let out = describeSyntaxKind(expected[0]);
|
||||||
|
if (expected.length === 1) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
for (let i = 1; i < expected.length-1; i++) {
|
||||||
|
const kind = expected[i];
|
||||||
|
out += ', ' + describeSyntaxKind(kind);
|
||||||
|
}
|
||||||
|
out += ' or ' + describeSyntaxKind(expected[expected.length-1])
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParseError extends Error {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public actual: Token,
|
||||||
|
public expected: SyntaxKind[],
|
||||||
|
) {
|
||||||
|
super(`got '${actual.text}' but expected ${describeExpected(expected)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConstructor(token: Token): boolean {
|
||||||
|
return token.kind === SyntaxKind.Identifier
|
||||||
|
&& token.text[0].toUpperCase() === token.text[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum OperatorMode {
|
||||||
|
None = 0,
|
||||||
|
Prefix = 1,
|
||||||
|
InfixL = 2,
|
||||||
|
InfixR = 4,
|
||||||
|
Suffix = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OperatorInfo {
|
||||||
|
name: string,
|
||||||
|
mode: OperatorMode,
|
||||||
|
precedence?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Parser {
|
||||||
|
|
||||||
|
private exprOperators = new MultiDict<string, OperatorInfo>();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public tokens: Stream<Token>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private getToken(): Token {
|
||||||
|
return this.tokens.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private peekToken(offset = 1): Token {
|
||||||
|
return this.tokens.peek(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertToken<K extends Token['kind']>(token: Token, expectedKind: K): void {
|
||||||
|
if (token.kind !== expectedKind) {
|
||||||
|
this.raiseParseError(token, [ expectedKind ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private expectToken<K extends TokenKind>(expectedKind: K): Token & { kind: K } {
|
||||||
|
const token = this.getToken();
|
||||||
|
if (token.kind !== expectedKind) {
|
||||||
|
this.raiseParseError(token, [ expectedKind ])
|
||||||
|
}
|
||||||
|
return token as Token & { kind: K };
|
||||||
|
}
|
||||||
|
|
||||||
|
private raiseParseError(actual: Token, expected: SyntaxKind[]): never {
|
||||||
|
throw new ParseError(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private peekTokenAfterModifiers(): Token {
|
||||||
|
let t0;
|
||||||
|
for (let i = 1;;i++) {
|
||||||
|
t0 = this.peekToken(i);
|
||||||
|
if (t0.kind !== SyntaxKind.PubKeyword) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isPrefixOperator(token: Token): boolean {
|
||||||
|
const name = token.text;
|
||||||
|
for (const operator of this.exprOperators.get(name)) {
|
||||||
|
if (operator.mode & OperatorMode.Prefix) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isBinaryOperator(token: Token): boolean {
|
||||||
|
return token.kind === SyntaxKind.CustomOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseReferenceTypeExpression(): ReferenceTypeExpression {
|
||||||
|
const name = this.expectToken(SyntaxKind.Identifier);
|
||||||
|
return new ReferenceTypeExpression([], name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseTypeExpression(): TypeExpression {
|
||||||
|
const t0 = this.peekToken();
|
||||||
|
switch (t0.kind) {
|
||||||
|
case SyntaxKind.Identifier:
|
||||||
|
return this.parseReferenceTypeExpression();
|
||||||
|
default:
|
||||||
|
throw new ParseError(t0, [ SyntaxKind.Identifier ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseConstantExpression(): ConstantExpression {
|
||||||
|
const token = this.getToken()
|
||||||
|
if (token.kind !== SyntaxKind.StringLiteral
|
||||||
|
&& token.kind !== SyntaxKind.Integer) {
|
||||||
|
this.raiseParseError(token, [ SyntaxKind.StringLiteral, SyntaxKind.Integer ])
|
||||||
|
}
|
||||||
|
return new ConstantExpression(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseReferenceExpression(): ReferenceExpression {
|
||||||
|
const modulePath: Array<[Identifier, Dot]> = [];
|
||||||
|
let name = this.expectToken(SyntaxKind.Identifier)
|
||||||
|
for (;;) {
|
||||||
|
const t1 = this.peekToken()
|
||||||
|
if (t1.kind !== SyntaxKind.Dot) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
modulePath.push([name, t1]);
|
||||||
|
name = this.expectToken(SyntaxKind.Identifier)
|
||||||
|
}
|
||||||
|
return new ReferenceExpression(modulePath, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseExpressionWithParens(): Expression {
|
||||||
|
const t0 = this.expectToken(SyntaxKind.LParen)
|
||||||
|
const t1 = this.peekToken();
|
||||||
|
if (t1.kind === SyntaxKind.RParen) {
|
||||||
|
this.getToken();
|
||||||
|
return new TupleExpression(t0, [], t1);
|
||||||
|
}
|
||||||
|
if (isConstructor(t1)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseExpressionNoOperators(): Expression {
|
||||||
|
const t0 = this.peekToken();
|
||||||
|
switch (t0.kind) {
|
||||||
|
case SyntaxKind.LParen:
|
||||||
|
return this.parseExpressionWithParens();
|
||||||
|
case SyntaxKind.Identifier:
|
||||||
|
return this.parseReferenceExpression();
|
||||||
|
case SyntaxKind.Integer:
|
||||||
|
case SyntaxKind.StringLiteral:
|
||||||
|
return this.parseConstantExpression();
|
||||||
|
default:
|
||||||
|
this.raiseParseError(t0, [
|
||||||
|
SyntaxKind.TupleExpression,
|
||||||
|
SyntaxKind.NestedExpression,
|
||||||
|
SyntaxKind.ConstantExpression,
|
||||||
|
SyntaxKind.ReferenceExpression
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseUnaryExpression(): Expression {
|
||||||
|
let out = this.parseExpressionNoOperators()
|
||||||
|
const prefixOperators = [];
|
||||||
|
for (;;) {
|
||||||
|
const t0 = this.peekToken();
|
||||||
|
if (!this.isPrefixOperator(t0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prefixOperators.push(t0);
|
||||||
|
this.getToken()
|
||||||
|
}
|
||||||
|
for (let i = prefixOperators.length-1; i >= 0; i--) {
|
||||||
|
const op = prefixOperators[i];
|
||||||
|
out = new PrefixExpression(op, out);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseExpressionWithBinaryOperator(lhs: Expression, minPrecedence: number) {
|
||||||
|
for (;;) {
|
||||||
|
const t0 = this.peekToken();
|
||||||
|
if (!this.isBinaryOperator(t0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseExpression(): Expression {
|
||||||
|
const lhs = this.parseUnaryExpression();
|
||||||
|
return this.parseExpressionWithBinaryOperator(lhs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseStructDeclaration(): StructDeclaration {
|
||||||
|
const structKeyword = this.expectToken(SyntaxKind.StructKeyword);
|
||||||
|
const name = this.expectToken(SyntaxKind.Identifier);
|
||||||
|
const t2 = this.peekToken()
|
||||||
|
let members = null;
|
||||||
|
if (t2.kind === SyntaxKind.BlockStart) {
|
||||||
|
this.getToken();
|
||||||
|
members = [];
|
||||||
|
for (;;) {
|
||||||
|
const name = this.expectToken(SyntaxKind.Identifier);
|
||||||
|
const colon = this.expectToken(SyntaxKind.Colon);
|
||||||
|
const typeExpr = this.parseTypeExpression();
|
||||||
|
const member = new StructDeclarationField(name, colon, typeExpr);
|
||||||
|
members.push(member);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.assertToken(t2, SyntaxKind.LineFoldEnd);
|
||||||
|
}
|
||||||
|
return new StructDeclaration(structKeyword, name, members);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parsePattern(): Pattern {
|
||||||
|
const t0 = this.peekToken();
|
||||||
|
switch (t0.kind) {
|
||||||
|
case SyntaxKind.Identifier:
|
||||||
|
this.getToken();
|
||||||
|
return new BindPattern(t0);
|
||||||
|
default:
|
||||||
|
this.raiseParseError(t0, [ SyntaxKind.Identifier ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseParam(): Param {
|
||||||
|
const pattern = this.parsePattern();
|
||||||
|
return new Param(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseDeclartionWithLetKeyword(): LetDeclaration {
|
||||||
|
let t0 = this.getToken();
|
||||||
|
let pubKeyword = null;
|
||||||
|
let mutKeyword = null;
|
||||||
|
if (t0.kind === SyntaxKind.PubKeyword) {
|
||||||
|
pubKeyword = t0;
|
||||||
|
t0 = this.getToken();
|
||||||
|
}
|
||||||
|
if (t0.kind !== SyntaxKind.LetKeyword) {
|
||||||
|
this.raiseParseError(t0, [ SyntaxKind.LetKeyword ]);
|
||||||
|
}
|
||||||
|
const t1 = this.peekToken();
|
||||||
|
if (t1.kind === SyntaxKind.MutKeyword) {
|
||||||
|
this.getToken();
|
||||||
|
mutKeyword = t1;
|
||||||
|
}
|
||||||
|
const pattern = this.parsePattern();
|
||||||
|
const params = [];
|
||||||
|
for (;;) {
|
||||||
|
const t2 = this.peekToken();
|
||||||
|
if (t2.kind === SyntaxKind.Colon
|
||||||
|
|| t2.kind === SyntaxKind.BlockStart
|
||||||
|
|| t2.kind === SyntaxKind.Equals
|
||||||
|
|| t2.kind === SyntaxKind.LineFoldEnd) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
params.push(this.parseParam());
|
||||||
|
}
|
||||||
|
let typeAssert = null;
|
||||||
|
let t3 = this.getToken();
|
||||||
|
if (t3.kind === SyntaxKind.Colon) {
|
||||||
|
const typeExpression = this.parseTypeExpression();
|
||||||
|
typeAssert = new TypeAssert(t3, typeExpression);
|
||||||
|
t3 = this.getToken();
|
||||||
|
}
|
||||||
|
let body = null;
|
||||||
|
switch (t3.kind) {
|
||||||
|
case SyntaxKind.BlockStart:
|
||||||
|
{
|
||||||
|
const elements = [];
|
||||||
|
for (;;) {
|
||||||
|
const t4 = this.peekToken();
|
||||||
|
if (t4.kind === SyntaxKind.BlockEnd) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
elements.push(this.parseLetBodyElement());
|
||||||
|
}
|
||||||
|
body = new BlockBody(t3, elements);
|
||||||
|
t3 = this.getToken();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.Equals:
|
||||||
|
{
|
||||||
|
const expression = this.parseExpression();
|
||||||
|
body = new ExprBody(t3, expression);
|
||||||
|
t3 = this.getToken();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.LineFoldEnd:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (t3.kind !== SyntaxKind.LineFoldEnd) {
|
||||||
|
this.raiseParseError(t3, [ SyntaxKind.LineFoldEnd ]);
|
||||||
|
}
|
||||||
|
return new LetDeclaration(
|
||||||
|
pubKeyword,
|
||||||
|
t0,
|
||||||
|
mutKeyword,
|
||||||
|
pattern,
|
||||||
|
params,
|
||||||
|
typeAssert,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseExpressionStatement(): ExpressionStatement {
|
||||||
|
const expression = this.parseExpression();
|
||||||
|
this.expectToken(SyntaxKind.LineFoldEnd)
|
||||||
|
return new ExpressionStatement(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseImportDeclaration(): ImportDeclaration {
|
||||||
|
const importKeyword = this.expectToken(SyntaxKind.ImportKeyword);
|
||||||
|
const importSource = this.expectToken(SyntaxKind.StringLiteral);
|
||||||
|
return new ImportDeclaration(importKeyword, importSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseSourceFileElement(): SourceFileElement {
|
||||||
|
|
||||||
|
const t0 = this.peekTokenAfterModifiers();
|
||||||
|
switch (t0.kind) {
|
||||||
|
case SyntaxKind.LetKeyword:
|
||||||
|
return this.parseDeclartionWithLetKeyword();
|
||||||
|
case SyntaxKind.ImportKeyword:
|
||||||
|
return this.parseImportDeclaration();
|
||||||
|
case SyntaxKind.StructKeyword:
|
||||||
|
return this.parseStructDeclaration();
|
||||||
|
default:
|
||||||
|
return this.parseExpressionStatement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseSourceFile(): SourceFile {
|
||||||
|
const elements = [];
|
||||||
|
for (;;) {
|
||||||
|
const t0 = this.peekToken();
|
||||||
|
if (t0.kind === SyntaxKind.EndOfFile) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const element = this.parseSourceFileElement();
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
return new SourceFile(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
389
src/scanner.ts
Normal file
389
src/scanner.ts
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
SyntaxKind,
|
||||||
|
Token,
|
||||||
|
Identifier,
|
||||||
|
StringLiteral,
|
||||||
|
EndOfFile,
|
||||||
|
BlockStart,
|
||||||
|
BlockEnd,
|
||||||
|
LineFoldEnd,
|
||||||
|
PubKeyword,
|
||||||
|
MutKeyword,
|
||||||
|
LetKeyword,
|
||||||
|
ImportKeyword,
|
||||||
|
TypeKeyword,
|
||||||
|
TextPosition,
|
||||||
|
Colon,
|
||||||
|
Comma,
|
||||||
|
Equals,
|
||||||
|
LParen,
|
||||||
|
RParen,
|
||||||
|
LBrace,
|
||||||
|
LBracket,
|
||||||
|
RBrace,
|
||||||
|
RBracket,
|
||||||
|
CustomOperator,
|
||||||
|
} from "./cst"
|
||||||
|
import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics"
|
||||||
|
import { Stream, BufferedStream } from "./util";
|
||||||
|
|
||||||
|
const EOF = '\uFFFF'
|
||||||
|
|
||||||
|
function isWhiteSpace(ch: string): boolean {
|
||||||
|
return /[\r\n\t ]/.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIdentPart(ch: string): boolean {
|
||||||
|
return /[a-zA-Z0-9_]/.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIdentStart(ch: string): boolean {
|
||||||
|
return /[a-zA-Z_]/.test(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOperatorPart(ch: string): boolean {
|
||||||
|
return /\+-*\/%^&|$<>!?=/.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanError extends Error {}
|
||||||
|
|
||||||
|
export class Scanner extends BufferedStream<Token> {
|
||||||
|
|
||||||
|
private currLine = 1;
|
||||||
|
private currColumn = 1;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public text: string,
|
||||||
|
public textOffset: number = 0,
|
||||||
|
public diagnostics: Diagnostics,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
private peekChar(offset = 1): string {
|
||||||
|
const i = this.textOffset + offset - 1;
|
||||||
|
return i < this.text.length ? this.text[i] : EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getChar(): string {
|
||||||
|
const ch = this.textOffset < this.text.length
|
||||||
|
? this.text[this.textOffset++]
|
||||||
|
: EOF
|
||||||
|
if (ch === '\n') {
|
||||||
|
this.currLine++;
|
||||||
|
this.currColumn = 1;
|
||||||
|
} else {
|
||||||
|
this.currColumn++;
|
||||||
|
}
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private takeWhile(pred: (ch: string) => boolean): string {
|
||||||
|
let out = ''
|
||||||
|
for (;;) {
|
||||||
|
const c0 = this.peekChar()
|
||||||
|
if (!pred(c0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.getChar()
|
||||||
|
out += c0;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCurrentPosition(): TextPosition {
|
||||||
|
return new TextPosition(
|
||||||
|
this.textOffset,
|
||||||
|
this.currLine,
|
||||||
|
this.currColumn
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public read(): Token {
|
||||||
|
|
||||||
|
let c0: string;
|
||||||
|
|
||||||
|
// Skip whitespace and comments
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
c0 = this.peekChar();
|
||||||
|
if (isWhiteSpace(c0)) {
|
||||||
|
this.getChar();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c0 === '#') {
|
||||||
|
this.getChar();
|
||||||
|
for (;;) {
|
||||||
|
const c1 = this.getChar();
|
||||||
|
if (c1 === '\n' || c1 === EOF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We failed to match a newline or line comment, so there's nothing to skip
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPos = this.getCurrentPosition();
|
||||||
|
this.getChar();
|
||||||
|
|
||||||
|
switch (c0) {
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
const startPos = this.getCurrentPosition();
|
||||||
|
let contents = '';
|
||||||
|
let escaping = false;
|
||||||
|
for (;;) {
|
||||||
|
const c1 = this.getChar();
|
||||||
|
if (escaping) {
|
||||||
|
switch (c1) {
|
||||||
|
case 'a': contents += '\a'; break;
|
||||||
|
case 'b': contents += '\b'; break;
|
||||||
|
case 'f': contents += '\f'; break;
|
||||||
|
case 'n': contents += '\n'; break;
|
||||||
|
case 'r': contents += '\r'; break;
|
||||||
|
case 't': contents += '\t'; break;
|
||||||
|
case 'v': contents += '\v'; break;
|
||||||
|
case '0': contents += '\0'; break;
|
||||||
|
case '\'': contents += '\''; break;
|
||||||
|
case '\"': contents += '\"'; break;
|
||||||
|
default:
|
||||||
|
this.diagnostics.add(new UnexpectedCharDiagnostic(this.text, this.textOffset, c1));
|
||||||
|
throw new ScanError();
|
||||||
|
}
|
||||||
|
escaping = false;
|
||||||
|
} else {
|
||||||
|
if (c1 === '"') {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
contents += c1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new StringLiteral(contents, startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EOF:
|
||||||
|
{
|
||||||
|
return new EndOfFile(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
case '(': return new LParen(startPos);
|
||||||
|
case ')': return new RParen(startPos);
|
||||||
|
case '[': return new LBracket(startPos);
|
||||||
|
case ']': return new RBracket(startPos);
|
||||||
|
case '{': return new LBrace(startPos);
|
||||||
|
case '}': return new RBrace(startPos);
|
||||||
|
case ',': return new Comma(startPos);
|
||||||
|
case ':': return new Colon(startPos);
|
||||||
|
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
case '*':
|
||||||
|
case '/':
|
||||||
|
case '%':
|
||||||
|
case '&':
|
||||||
|
case '^':
|
||||||
|
case '|':
|
||||||
|
case '$':
|
||||||
|
case '<':
|
||||||
|
case '>':
|
||||||
|
case '=':
|
||||||
|
case '!':
|
||||||
|
case '?':
|
||||||
|
{
|
||||||
|
const text = c0 + this.takeWhile(isOperatorPart);
|
||||||
|
if (text === '=') {
|
||||||
|
return new Equals(startPos);
|
||||||
|
} else if (text.endsWith('=') && text[text.length-2] !== '=') {
|
||||||
|
return new Assignment(startPos);
|
||||||
|
} else {
|
||||||
|
return new CustomOperator(text, startPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
case 'b':
|
||||||
|
case 'c':
|
||||||
|
case 'd':
|
||||||
|
case 'e':
|
||||||
|
case 'f':
|
||||||
|
case 'g':
|
||||||
|
case 'h':
|
||||||
|
case 'i':
|
||||||
|
case 'j':
|
||||||
|
case 'k':
|
||||||
|
case 'l':
|
||||||
|
case 'm':
|
||||||
|
case 'n':
|
||||||
|
case 'o':
|
||||||
|
case 'p':
|
||||||
|
case 'q':
|
||||||
|
case 'r':
|
||||||
|
case 's':
|
||||||
|
case 't':
|
||||||
|
case 'u':
|
||||||
|
case 'v':
|
||||||
|
case 'w':
|
||||||
|
case 'x':
|
||||||
|
case 'y':
|
||||||
|
case 'z':
|
||||||
|
case 'A':
|
||||||
|
case 'B':
|
||||||
|
case 'C':
|
||||||
|
case 'D':
|
||||||
|
case 'E':
|
||||||
|
case 'F':
|
||||||
|
case 'G':
|
||||||
|
case 'H':
|
||||||
|
case 'I':
|
||||||
|
case 'J':
|
||||||
|
case 'K':
|
||||||
|
case 'L':
|
||||||
|
case 'M':
|
||||||
|
case 'N':
|
||||||
|
case 'O':
|
||||||
|
case 'P':
|
||||||
|
case 'Q':
|
||||||
|
case 'R':
|
||||||
|
case 'S':
|
||||||
|
case 'T':
|
||||||
|
case 'U':
|
||||||
|
case 'V':
|
||||||
|
case 'W':
|
||||||
|
case 'X':
|
||||||
|
case 'Y':
|
||||||
|
case 'Z':
|
||||||
|
case '_':
|
||||||
|
{
|
||||||
|
const text = c0 + this.takeWhile(isIdentPart);
|
||||||
|
switch (text) {
|
||||||
|
case 'import': return new ImportKeyword(startPos);
|
||||||
|
case 'pub': return new PubKeyword(startPos);
|
||||||
|
case 'mut': return new MutKeyword(startPos);
|
||||||
|
case 'let': return new LetKeyword(startPos);
|
||||||
|
case 'import': return new ImportKeyword(startPos);
|
||||||
|
case 'return': return new ReturnKeyword(startPos);
|
||||||
|
case 'type': return new TypeKeyword(startPos);
|
||||||
|
default:
|
||||||
|
return new Identifier(text, startPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
// Nothing matched, so the current character is unrecognisable
|
||||||
|
this.diagnostics.add(new UnexpectedCharDiagnostic(this.text, this.textOffset, c0));
|
||||||
|
throw new ScanError();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum FrameType {
|
||||||
|
Block,
|
||||||
|
LineFold,
|
||||||
|
}
|
||||||
|
|
||||||
|
const INIT_POS = new TextPosition(0, 0, 0);
|
||||||
|
|
||||||
|
export class Punctuator extends BufferedStream<Token> {
|
||||||
|
|
||||||
|
private referencePositions: TextPosition[] = [ INIT_POS ];
|
||||||
|
|
||||||
|
private frameTypes: FrameType[] = [ FrameType.Block ];
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private tokens: Stream<Token>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public read(): Token {
|
||||||
|
|
||||||
|
const refPos = this.referencePositions[this.referencePositions.length-1];
|
||||||
|
const frameType = this.frameTypes[this.frameTypes.length-1];
|
||||||
|
const t0 = this.tokens.peek(1);
|
||||||
|
|
||||||
|
if (t0.kind === SyntaxKind.EndOfFile) {
|
||||||
|
if (this.frameTypes.length === 1) {
|
||||||
|
return t0;
|
||||||
|
}
|
||||||
|
this.frameTypes.pop();
|
||||||
|
switch (frameType) {
|
||||||
|
case FrameType.LineFold:
|
||||||
|
return new LineFoldEnd(t0.getStartPosition());
|
||||||
|
case FrameType.Block:
|
||||||
|
return new BlockEnd(t0.getStartPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (frameType) {
|
||||||
|
|
||||||
|
case FrameType.LineFold:
|
||||||
|
{
|
||||||
|
|
||||||
|
// This important check verifies we're still inside the line-fold. If
|
||||||
|
// we aren't, we need to clean up the stack a bit and eventually return
|
||||||
|
// a token that indicates the line-fold ended.
|
||||||
|
if (t0.getStartLine() > refPos.line
|
||||||
|
&& t0.getStartColumn() <= refPos.column) {
|
||||||
|
this.frameTypes.pop();
|
||||||
|
this.referencePositions.pop();
|
||||||
|
return new LineFoldEnd(t0.getStartPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
const t1 = this.tokens.peek(2);
|
||||||
|
if (t0.kind === SyntaxKind.Dot && t0.getEndLine() < t1.getStartLine()) {
|
||||||
|
this.tokens.get();
|
||||||
|
this.frameTypes.push(FrameType.Block);
|
||||||
|
return new BlockStart(t0.getStartPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, this is an ordinary token that is part of the
|
||||||
|
// line-fold. Make sure to consume it and return it to the caller.
|
||||||
|
this.tokens.get();
|
||||||
|
return t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FrameType.Block:
|
||||||
|
{
|
||||||
|
|
||||||
|
if (t0.getStartColumn() <= refPos.column) {
|
||||||
|
|
||||||
|
// We only get here if the current token is less indented than the
|
||||||
|
// current reference token. Pop the block indicator and leave the
|
||||||
|
// reference position be for the edge case where the parent line-fold
|
||||||
|
// continues after the block.
|
||||||
|
this.frameTypes.pop();
|
||||||
|
return new BlockEnd(t0.getStartPosition());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frameTypes.push(FrameType.LineFold);
|
||||||
|
this.referencePositions.push(t0.getStartPosition());
|
||||||
|
|
||||||
|
// In theory, we could explictly issue a LineFoldStart and let all
|
||||||
|
// tokens be passed through in the FrameType.LineFold case. It does add
|
||||||
|
// more logic to the parser for no real benefit, which is why it was
|
||||||
|
// omitted.
|
||||||
|
this.tokens.get();
|
||||||
|
return t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
64
src/util.ts
Normal file
64
src/util.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
|
||||||
|
|
||||||
|
export class MultiDict<K, V> {
|
||||||
|
|
||||||
|
private mapping = new Map<K, V[]>();
|
||||||
|
|
||||||
|
public constructor(iterable?: Iterable<[K, V]>) {
|
||||||
|
if (iterable) {
|
||||||
|
for (const [key, value] of iterable) {
|
||||||
|
this.add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(key: K): Iterable<V> {
|
||||||
|
return this.mapping.get(key) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(key: K, value: V): void {
|
||||||
|
const values = this.mapping.get(key);
|
||||||
|
if (values) {
|
||||||
|
values.push(value);
|
||||||
|
} else {
|
||||||
|
this.mapping.set(key, [ value ])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public *[Symbol.iterator](): Iterator<[K, V]> {
|
||||||
|
for (const [key, values] of this.mapping) {
|
||||||
|
for (const value of values) {
|
||||||
|
yield [key, value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stream<T> {
|
||||||
|
get(): T;
|
||||||
|
peek(offset?: number): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BufferedStream<T> {
|
||||||
|
|
||||||
|
private buffer: Array<T> = [];
|
||||||
|
|
||||||
|
public abstract read(): T;
|
||||||
|
|
||||||
|
public get(): T {
|
||||||
|
if (this.buffer.length > 0) {
|
||||||
|
return this.buffer.shift()!;
|
||||||
|
}
|
||||||
|
return this.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
public peek(offset = 1): T {
|
||||||
|
while (this.buffer.length < offset) {
|
||||||
|
this.buffer.push(this.read());
|
||||||
|
}
|
||||||
|
return this.buffer[offset-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2016",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./lib",
|
||||||
|
"removeComments": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue