diff options
Diffstat (limited to '')
234 files changed, 12272 insertions, 0 deletions
diff --git a/llparse-builder/.gitignore b/llparse-builder/.gitignore new file mode 100644 index 0000000..5e67ab3 --- /dev/null +++ b/llparse-builder/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +npm-debug.log +lib/ diff --git a/llparse-builder/.travis.yml b/llparse-builder/.travis.yml new file mode 100644 index 0000000..b5efd79 --- /dev/null +++ b/llparse-builder/.travis.yml @@ -0,0 +1,4 @@ +sudo: false +language: node_js +node_js: + - "stable" diff --git a/llparse-builder/README.md b/llparse-builder/README.md new file mode 100644 index 0000000..522fba2 --- /dev/null +++ b/llparse-builder/README.md @@ -0,0 +1,32 @@ +# llparse-builder +[![Build Status](https://secure.travis-ci.org/indutny/llparse-builder.svg)](http://travis-ci.org/indutny/llparse-builder) +[![NPM version](https://badge.fury.io/js/llparse-builder.svg)](https://badge.fury.io/js/llparse-builder) + +See [llparse][0]. + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +[0]: https://github.com/indutny/llparse diff --git a/llparse-builder/package-lock.json b/llparse-builder/package-lock.json new file mode 100644 index 0000000..5e76f34 --- /dev/null +++ b/llparse-builder/package-lock.json @@ -0,0 +1,1466 @@ +{ + "name": "llparse-builder", + "version": "1.5.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/node": { + "version": "14.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", + "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "binary-search": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", + "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + } + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.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==", + "dev": true, + "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==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "mocha": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", + "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.2", + "debug": "4.1.1", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "4.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", + "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "decamelize": "^1.2.0", + "flat": "^4.1.0", + "is-plain-obj": "^1.1.0", + "yargs": "^14.2.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/llparse-builder/package.json b/llparse-builder/package.json new file mode 100644 index 0000000..1c1ac54 --- /dev/null +++ b/llparse-builder/package.json @@ -0,0 +1,48 @@ +{ + "name": "llparse-builder", + "version": "1.5.2", + "description": "Build graph for consumption in LLParse", + "main": "lib/builder.js", + "types": "lib/builder.d.ts", + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf lib", + "prepare": "npm run clean && npm run build", + "lint": "tslint -c tslint.json src/*.ts src/**/*.ts src/**/**/*.ts test/*.ts", + "mocha": "mocha -r ts-node/register/type-check --reporter spec test/*-test.ts", + "test": "npm run mocha && npm run lint" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/indutny/llparse-builder.git" + }, + "keywords": [ + "llparse", + "builder", + "llvm", + "bitcode" + ], + "author": "Fedor Indutny <fedor@indutny.com> (http://darksi.de/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/indutny/llparse-builder/issues" + }, + "homepage": "https://github.com/indutny/llparse-builder#readme", + "devDependencies": { + "@types/mocha": "^8.0.3", + "@types/node": "^14.11.8", + "mocha": "^8.1.3", + "ts-node": "^9.0.0", + "tslint": "^5.20.1", + "typescript": "^4.0.3" + }, + "dependencies": { + "@types/debug": "4.1.5 ", + "binary-search": "^1.3.6", + "debug": "^4.2.0" + } +} diff --git a/llparse-builder/src/builder.ts b/llparse-builder/src/builder.ts new file mode 100644 index 0000000..a335a85 --- /dev/null +++ b/llparse-builder/src/builder.ts @@ -0,0 +1,147 @@ +import * as code from './code'; +import * as node from './node'; +import { Property, PropertyType } from './property'; +import { Span } from './span'; +import * as transform from './transform'; + +export { code, node, transform, Property, PropertyType, Span }; +export { Edge } from './edge'; +export { LoopChecker } from './loop-checker'; +export { ISpanAllocatorResult, SpanAllocator } from './span-allocator'; +export { Reachability } from './reachability'; + +/** + * Construct parsing graph for later use in `llparse`. + */ +export class Builder { + /** + * API for creating external callbacks and intrinsic operations. + */ + public readonly code: code.Creator = new code.Creator(); + + /** + * API for creating character transforms for use in nodes created with + * `builder.node()` + */ + public readonly transform: transform.Creator = new transform.Creator(); + + private readonly privProperties: Map<string, Property> = new Map(); + + // Various nodes + + /** + * Create regular node for matching characters and sequences. + * + * @param name Node name + */ + public node(name: string): node.Match { + return new node.Match(name); + } + + /** + * Create terminal error node. Returns error code to user, and sets reason + * in the parser's state object. + * + * This node does not consume any bytes upon execution. + * + * @param errorCode Integer error code + * @param reason Error description + */ + public error(errorCode: number, reason: string): node.Error { + return new node.Error(errorCode, reason); + } + + /** + * Create invoke node that calls either external user callback or an + * intrinsic operation. + * + * This node does not consume any bytes upon execution. + * + * NOTE: When `.invoke()` is a target of `node().select()` - callback must + * have signature that accepts `.select()`'s value, otherwise it must be of + * the signature that takes no such value. + * + * @param fn Code instance to invoke + * @param map Object with integer keys and `Node` values. Describes + * nodes that are visited upon receiving particular + * return integer value + * @param otherwise Convenience `Node` argument. Effect is the same as calling + * `p.invoke(...).otherwise(node)` + */ + public invoke(fn: code.Code, map?: node.IInvokeMap | node.Node, + otherwise?: node.Node): node.Invoke { + let res: node.Invoke; + + // `.invoke(name)` + if (map === undefined) { + res = new node.Invoke(fn, {}); + // `.invoke(name, otherwise)` + } else if (map instanceof node.Node) { + res = new node.Invoke(fn, {}); + otherwise = map; + } else { + res = new node.Invoke(fn, map as node.IInvokeMap); + } + + if (otherwise !== undefined) { + res.otherwise(otherwise); + } + return res; + } + + /** + * Create node that consumes number of bytes specified by value of the + * state's property with name in `field` argument. + * + * @param field Property name to use + */ + public consume(field: string): node.Consume { + return new node.Consume(field); + } + + /** + * Create non-terminal node that returns `errorCode` as error number to + * user, but still allows feeding more data to the parser. + * + * This node does not consume any bytes upon execution. + * + * @param errorCode Integer error code + * @param reason Error description + */ + public pause(errorCode: number, reason: string): node.Pause { + return new node.Pause(errorCode, reason); + } + + // Span + + /** + * Create Span with given `callback`. + * + * @param callback External span callback, must be result of + * `.code.span(...)` + */ + public span(callback: code.Span): Span { + return new Span(callback); + } + + // Custom property API + + /** + * Allocate space for property in parser's state. + */ + public property(ty: PropertyType, name: string): void { + if (this.privProperties.has(name)) { + throw new Error(`Duplicate property with a name: "${name}"`); + } + + const prop = new Property(ty, name); + this.privProperties.set(name, prop); + } + + /** + * Return list of all allocated properties in parser's state. + */ + public get properties(): ReadonlyArray<Property> { + return Array.from(this.privProperties.values()); + } +} diff --git a/llparse-builder/src/code/and.ts b/llparse-builder/src/code/and.ts new file mode 100644 index 0000000..5f78675 --- /dev/null +++ b/llparse-builder/src/code/and.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class And extends FieldValue { + constructor(field: string, value: number) { + super('match', 'and', field, value); + } +} diff --git a/llparse-builder/src/code/base.ts b/llparse-builder/src/code/base.ts new file mode 100644 index 0000000..00b479f --- /dev/null +++ b/llparse-builder/src/code/base.ts @@ -0,0 +1,16 @@ +export type Signature = 'match' | 'value'; + +/** + * Base code class. + */ +export abstract class Code { + /** + * @param signature Code signature to be used. `match` means that code takes + * no input value (from `.select()`), otherwise it must be + * `value` + * @param name External function or intrinsic name. + */ + constructor(public readonly signature: Signature, + public readonly name: string) { + } +} diff --git a/llparse-builder/src/code/creator.ts b/llparse-builder/src/code/creator.ts new file mode 100644 index 0000000..98f9296 --- /dev/null +++ b/llparse-builder/src/code/creator.ts @@ -0,0 +1,184 @@ +import * as code from './'; + +/** + * API for creating external callbacks and intrinsic operations. + */ +export class Creator { + // Callbacks to external C functions + + /** + * Create an external callback that **has no** `value` argument. + * + * This callback can be used in all `Invoke` nodes except those that are + * targets of `.select()` method. + * + * C signature of callback must be: + * + * ```c + * int name(llparse_t* state, const char* p, const char* endp) + * ``` + * + * Where `llparse_t` is parser state's type name. + * + * @param name External function name. + */ + public match(name: string): code.Match { + return new code.Match(name); + } + + /** + * Create an external callback that **has** `value` argument. + * + * This callback can be used only in `Invoke` nodes that are targets of + * `.select()` method. + * + * C signature of callback must be: + * + * ```c + * int name(llparse_t* state, const char* p, const char* endp, int value) + * ``` + * + * Where `llparse_t` is parser state's type name. + * + * @param name External function name. + */ + public value(name: string): code.Value { + return new code.Value(name); + } + + /** + * Create an external span callback. + * + * This callback can be used only in `Span` constructor. + * + * C signature of callback must be: + * + * ```c + * int name(llparse_t* state, const char* p, const char* endp) + * ``` + * + * NOTE: non-zero return value is treated as resumable error. + * + * @param name External function name. + */ + public span(name: string): code.Span { + return new code.Span(name); + } + + // Helpers + + /** + * Intrinsic operation. Stores `value` from `.select()` node into the state's + * property with the name specified by `field`, returns zero. + * + * state[field] = value; + * return 0; + * + * @param field Property name + */ + public store(field: string): code.Store { + return new code.Store(field); + } + + /** + * Intrinsic operation. Loads and returns state's property with the name + * specified by `field`. + * + * The value of the property is either truncated or zero-extended to fit into + * 32-bit unsigned integer. + * + * return state[field]; + * + * @param field Property name. + */ + public load(field: string): code.Load { + return new code.Load(field); + } + + /** + * Intrinsic operation. Takes `value` from `.select()`, state's property + * with the name `field` and does: + * + * field = state[field]; + * field *= options.base; + * field += value; + * state[field] = field; + * return 0; // or 1 on overflow + * + * Return values are: + * + * - 0 - success + * - 1 - overflow + * + * @param field Property name + * @param options See `code.MulAdd` documentation. + */ + public mulAdd(field: string, options: code.IMulAddOptions): code.MulAdd { + return new code.MulAdd(field, options); + } + + /** + * Intrinsic operation. Puts `value` integer into the state's property with + * the name specified by `field`. + * + * state[field] = value; + * return 0; + * + * @param field Property name + * @param value Integer value to be stored into the property. + */ + public update(field: string, value: number): code.Update { + return new code.Update(field, value); + } + + /** + * Intrinsic operation. Returns 1 if the integer `value` is equal to the + * state's property with the name specified by `field`. + * + * return state[field] === value ? 1 : 0; + * + * @param field Property name + * @param value Integer value to be checked against. + */ + public isEqual(field: string, value: number): code.IsEqual { + return new code.IsEqual(field, value); + } + + /** + * Intrinsic operation. + * + * state[field] &= value + * return 0; + * + * @param field Property name + * @param value Integer value + */ + public and(field: string, value: number): code.And { + return new code.And(field, value); + } + + /** + * Intrinsic operation. + * + * state[field] |= value + * return 0; + * + * @param field Property name + * @param value Integer value + */ + public or(field: string, value: number): code.Or { + return new code.Or(field, value); + } + + /** + * Intrinsic operation. + * + * return (state[field] & value) == value ? 1 : 0; + * + * @param field Property name + * @param value Integer value + */ + public test(field: string, value: number): code.Test { + return new code.Test(field, value); + } +} diff --git a/llparse-builder/src/code/field-value.ts b/llparse-builder/src/code/field-value.ts new file mode 100644 index 0000000..2ceea69 --- /dev/null +++ b/llparse-builder/src/code/field-value.ts @@ -0,0 +1,9 @@ +import { Signature } from './base'; +import { Field } from './field'; + +export abstract class FieldValue extends Field { + constructor(signature: Signature, name: string, field: string, + public readonly value: number) { + super(signature, name, field); + } +} diff --git a/llparse-builder/src/code/field.ts b/llparse-builder/src/code/field.ts new file mode 100644 index 0000000..af58c84 --- /dev/null +++ b/llparse-builder/src/code/field.ts @@ -0,0 +1,10 @@ +import * as assert from 'assert'; +import { Code, Signature } from './base'; + +export abstract class Field extends Code { + constructor(signature: Signature, name: string, + public readonly field: string) { + super(signature, name + '_' + field); + assert(!/^_/.test(field), 'Can\'t access internal field from user code'); + } +} diff --git a/llparse-builder/src/code/index.ts b/llparse-builder/src/code/index.ts new file mode 100644 index 0000000..7a651e3 --- /dev/null +++ b/llparse-builder/src/code/index.ts @@ -0,0 +1,15 @@ +export { Code } from './base'; +export { Creator } from './creator'; +export { Field } from './field'; +export { FieldValue } from './field-value'; +export { IsEqual } from './is-equal'; +export { Load } from './load'; +export { Match } from './match'; +export { IMulAddOptions, MulAdd } from './mul-add'; +export { Or } from './or'; +export { And } from './and'; +export { Span } from './span'; +export { Store } from './store'; +export { Test } from './test'; +export { Update } from './update'; +export { Value } from './value'; diff --git a/llparse-builder/src/code/is-equal.ts b/llparse-builder/src/code/is-equal.ts new file mode 100644 index 0000000..91bb957 --- /dev/null +++ b/llparse-builder/src/code/is-equal.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class IsEqual extends FieldValue { + constructor(field: string, value: number) { + super('match', 'is_equal', field, value); + } +} diff --git a/llparse-builder/src/code/load.ts b/llparse-builder/src/code/load.ts new file mode 100644 index 0000000..9f3df2e --- /dev/null +++ b/llparse-builder/src/code/load.ts @@ -0,0 +1,7 @@ +import { Field } from './field'; + +export class Load extends Field { + constructor(field: string) { + super('match', 'load', field); + } +} diff --git a/llparse-builder/src/code/match.ts b/llparse-builder/src/code/match.ts new file mode 100644 index 0000000..631376a --- /dev/null +++ b/llparse-builder/src/code/match.ts @@ -0,0 +1,7 @@ +import { Code } from './base'; + +export class Match extends Code { + constructor(name: string) { + super('match', name); + } +} diff --git a/llparse-builder/src/code/mul-add.ts b/llparse-builder/src/code/mul-add.ts new file mode 100644 index 0000000..fd648ed --- /dev/null +++ b/llparse-builder/src/code/mul-add.ts @@ -0,0 +1,28 @@ +import { Field } from './field'; + +/** + * Options for `code.mulAdd()`. + */ +export interface IMulAddOptions { + /** Value to multiply the property with in the first step */ + readonly base: number; + + /** + * Maximum value of the property. If at any point of computation the + * intermediate result exceeds it - `mulAdd` returns 1 (overflow). + */ + readonly max?: number; + + /** + * If `true` - all arithmetics perfomed by `mulAdd` will be signed. + * + * Default value: `false` + */ + readonly signed?: boolean; +} + +export class MulAdd extends Field { + constructor(field: string, public readonly options: IMulAddOptions) { + super('value', 'mul_add', field); + } +} diff --git a/llparse-builder/src/code/or.ts b/llparse-builder/src/code/or.ts new file mode 100644 index 0000000..33bd402 --- /dev/null +++ b/llparse-builder/src/code/or.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class Or extends FieldValue { + constructor(field: string, value: number) { + super('match', 'or', field, value); + } +} diff --git a/llparse-builder/src/code/span.ts b/llparse-builder/src/code/span.ts new file mode 100644 index 0000000..b97e09e --- /dev/null +++ b/llparse-builder/src/code/span.ts @@ -0,0 +1,5 @@ +import { Match } from './match'; + +export class Span extends Match { + // no-op +} diff --git a/llparse-builder/src/code/store.ts b/llparse-builder/src/code/store.ts new file mode 100644 index 0000000..84abfef --- /dev/null +++ b/llparse-builder/src/code/store.ts @@ -0,0 +1,7 @@ +import { Field } from './field'; + +export class Store extends Field { + constructor(field: string) { + super('value', 'store', field); + } +} diff --git a/llparse-builder/src/code/test.ts b/llparse-builder/src/code/test.ts new file mode 100644 index 0000000..a9d0a22 --- /dev/null +++ b/llparse-builder/src/code/test.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class Test extends FieldValue { + constructor(field: string, value: number) { + super('match', 'test', field, value); + } +} diff --git a/llparse-builder/src/code/update.ts b/llparse-builder/src/code/update.ts new file mode 100644 index 0000000..de62476 --- /dev/null +++ b/llparse-builder/src/code/update.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class Update extends FieldValue { + constructor(field: string, value: number) { + super('match', 'update', field, value); + } +} diff --git a/llparse-builder/src/code/value.ts b/llparse-builder/src/code/value.ts new file mode 100644 index 0000000..06c6fd7 --- /dev/null +++ b/llparse-builder/src/code/value.ts @@ -0,0 +1,7 @@ +import { Code } from './base'; + +export class Value extends Code { + constructor(name: string) { + super('value', name); + } +} diff --git a/llparse-builder/src/edge.ts b/llparse-builder/src/edge.ts new file mode 100644 index 0000000..f6b55cc --- /dev/null +++ b/llparse-builder/src/edge.ts @@ -0,0 +1,54 @@ +import * as assert from 'assert'; + +import { Buffer } from 'buffer'; +import { Invoke, Node } from './node'; + +/** + * This class represents an edge in the parser graph. + */ +export class Edge { + /** + * Comparator for `.sort()` function. + */ + public static compare(a: Edge, b: Edge): number { + if (typeof a.key === 'number') { + return a.key - (b.key as number); + } + return a.key!.compare(b.key as Buffer); + } + + /** + * @param node Edge target + * @param noAdvance If `true` - the parent should not consume bytes before + * moving to the target `node` + * @param key `Buffer` for `node.Match`, `number` for `node.Invoke`, + * `undefined` for edges created with `.otherwise()` + * @param value `.select()` value associated with the edge + */ + constructor(public readonly node: Node, + public readonly noAdvance: boolean, + public readonly key: Buffer | number | undefined, + public readonly value: number | undefined) { + if (node instanceof Invoke) { + if (value === undefined) { + assert.strictEqual(node.code.signature, 'match', + 'Invalid Invoke\'s code signature'); + } else { + assert.strictEqual(node.code.signature, 'value', + 'Invalid Invoke\'s code signature'); + } + } else { + assert.strictEqual(value, undefined, + 'Attempting to pass value to non-Invoke node'); + } + + if (Buffer.isBuffer(key)) { + assert(key.length > 0, 'Invalid edge buffer length'); + + if (noAdvance) { + assert.strictEqual(key.length, 1, + 'Only 1-char keys are allowed in `noAdvance` edges'); + } + } + } +} diff --git a/llparse-builder/src/loop-checker/index.ts b/llparse-builder/src/loop-checker/index.ts new file mode 100644 index 0000000..5751955 --- /dev/null +++ b/llparse-builder/src/loop-checker/index.ts @@ -0,0 +1,205 @@ +import * as assert from 'assert'; +import * as debugAPI from 'debug'; + +import { Node } from '../node'; +import { Reachability } from '../reachability'; +import { Lattice } from './lattice'; + +const debug = debugAPI('llparse-builder:loop-checker'); + +const EMPTY_VALUE = new Lattice('empty'); +const ANY_VALUE = new Lattice('any'); + +/** + * This class implements a loop checker pass. The goal of this pass is to verify + * that the graph doesn't contain infinite loops. + */ +export class LoopChecker { + private readonly lattice: Map<Node, Lattice> = new Map(); + + // Just a cache of terminated keys + private readonly terminatedCache: Map<Node, Lattice> = new Map(); + + /** + * Run loop checker pass on a graph starting from `root`. + * + * Throws on failure. + * + * @param root Graph root node + */ + public check(root: Node): void { + const r = new Reachability(); + + const nodes = r.build(root); + + for (const node of nodes) { + debug('checking loops starting from %j', node.name); + + // Set initial lattice value for all nodes + this.clear(nodes); + + // Mark root as reachable with any value + this.lattice.set(node, ANY_VALUE); + + // Raise lattice values + let changed: Set<Node> = new Set([ root ]); + while (changed.size !== 0) { + if (debug.enabled) { + debug('changed %j', Array.from(changed).map((other) => other.name)); + } + + const next: Set<Node> = new Set(); + for (const changedNode of changed) { + this.propagate(changedNode, next); + } + changed = next; + } + + debug('lattice stabilized'); + + // Visit nodes and walk through reachable edges to detect loops + this.visit(node, []); + } + } + + private clear(nodes: ReadonlyArray<Node>): void { + for (const node of nodes) { + this.lattice.set(node, EMPTY_VALUE); + } + } + + private propagate(node: Node, changed: Set<Node>): void { + let value: Lattice = this.lattice.get(node)!; + debug('propagate(%j), initial value %j', node.name, value); + + // Terminate values that are consumed by `match`/`select` + const terminated = this.terminate(node, value, changed); + if (!terminated.isEqual(EMPTY_VALUE)) { + debug('node %j terminates %j', node.name, terminated); + value = value.subtract(terminated); + if (value.isEqual(EMPTY_VALUE)) { + return; + } + } + + const keysByTarget: Map<Node, Lattice> = new Map(); + // Propagate value through `.peek()`/`.otherwise()` edges + for (const edge of node.getAllEdges()) { + if (!edge.noAdvance) { + continue; + } + + let targetValue: Lattice; + if (keysByTarget.has(edge.node)) { + targetValue = keysByTarget.get(edge.node)!; + } else { + targetValue = this.lattice.get(edge.node)!; + } + + // `otherwise` or `Invoke`'s edges + if (edge.key === undefined || typeof edge.key === 'number') { + targetValue = targetValue.union(value); + } else { + // `.peek()` + const edgeValue = new Lattice([ edge.key[0] ]).intersect(value); + if (edgeValue.isEqual(EMPTY_VALUE)) { + continue; + } + + targetValue = targetValue.union(edgeValue); + } + + keysByTarget.set(edge.node, targetValue); + } + + for (const [ child, childValue ] of keysByTarget) { + debug('node %j propagates %j to %j', node.name, childValue, + child.name); + this.update(child, childValue, changed); + } + } + + private update(node: Node, newValue: Lattice, changed: Set<Node>): boolean { + const value = this.lattice.get(node)!; + if (newValue.isEqual(value)) { + return false; + } + + this.lattice.set(node, newValue); + changed.add(node); + return true; + } + + private terminate(node: Node, value: Lattice, changed: Set<Node>): Lattice { + if (this.terminatedCache.has(node)) { + return this.terminatedCache.get(node)!; + } + + const terminated: number[] = []; + for (const edge of node.getAllEdges()) { + if (edge.noAdvance) { + continue; + } + + // Ignore `otherwise` and `Invoke`'s edges + if (edge.key === undefined || typeof edge.key === 'number') { + continue; + } + + terminated.push(edge.key[0]); + } + + const result = new Lattice(terminated); + this.terminatedCache.set(node, result); + return result; + } + + private visit(node: Node, path: ReadonlyArray<Node>): void { + let value = this.lattice.get(node)!; + debug('enter %j, value is %j', node.name, value); + + const terminated = this.terminatedCache.has(node) ? + this.terminatedCache.get(node)! : EMPTY_VALUE; + if (!terminated.isEqual(EMPTY_VALUE)) { + debug('subtract terminated %j', terminated); + value = value.subtract(terminated); + if (value.isEqual(EMPTY_VALUE)) { + debug('terminated everything'); + return; + } + } + + for (const edge of node.getAllEdges()) { + if (!edge.noAdvance) { + continue; + } + + let edgeValue = value; + + // `otherwise` or `Invoke`'s edges + if (edge.key === undefined || typeof edge.key === 'number') { + // nothing to do + // `.peek()` + } else { + edgeValue = edgeValue.intersect(new Lattice([ edge.key[0] ])); + } + + // Ignore unreachable edges + if (edgeValue.isEqual(EMPTY_VALUE)) { + continue; + } + if (path.indexOf(edge.node) !== -1) { + if (path.length === 0) { + throw new Error( + `Detected loop in "${edge.node.name}" through "${edge.node.name}"`); + } + throw new Error( + `Detected loop in "${edge.node.name}" through chain ` + + `${path.map((parent) => '"' + parent.name + '"').join(' -> ')}`); + } + this.visit(edge.node, path.concat(edge.node)); + } + + debug('leave %j', node.name); + } +} diff --git a/llparse-builder/src/loop-checker/lattice.ts b/llparse-builder/src/loop-checker/lattice.ts new file mode 100644 index 0000000..8d2a7fe --- /dev/null +++ b/llparse-builder/src/loop-checker/lattice.ts @@ -0,0 +1,115 @@ +import * as assert from 'assert'; + +const MAX_VALUE = 256; +const WORD_SIZE = 32; +const SIZE = (MAX_VALUE / WORD_SIZE) | 0; +const WORD_FILL = -1 | 0; + +assert.strictEqual(MAX_VALUE % WORD_SIZE, 0); + +export type LatticeValue = 'empty' | ReadonlyArray<number> | 'any'; + +/** + * A fixed-size bitfield, really + */ +export class Lattice { + protected readonly words: number[]; + + constructor(value: LatticeValue) { + this.words = new Array(SIZE).fill(value === 'any' ? WORD_FILL : 0); + + if (Array.isArray(value)) { + for (const single of value) { + this.add(single); + } + } + } + + public check(bit: number): boolean { + assert(0 <= bit && bit < MAX_VALUE, 'Invalid bit'); + const index = (bit / WORD_SIZE) | 0; + const off = bit % WORD_SIZE; + return (this.words[index] & (1 << off)) !== 0; + } + + public union(other: Lattice): Lattice { + const result = new Lattice('empty'); + + for (let i = 0; i < SIZE; i++) { + result.words[i] = this.words[i] | other.words[i]; + } + + return result; + } + + public intersect(other: Lattice): Lattice { + const result = new Lattice('empty'); + + for (let i = 0; i < SIZE; i++) { + result.words[i] = this.words[i] & other.words[i]; + } + + return result; + } + + public subtract(other: Lattice): Lattice { + const result = new Lattice('empty'); + + for (let i = 0; i < SIZE; i++) { + result.words[i] = this.words[i] & (~other.words[i]); + } + + return result; + } + + public isEqual(other: Lattice): boolean { + if (this === other) { + return true; + } + + for (let i = 0; i < SIZE; i++) { + if (this.words[i] !== other.words[i]) { + return false; + } + } + return true; + } + + public *[Symbol.iterator](): Iterator<number> { + // TODO(indutny): improve speed if needed + for (let i = 0; i < MAX_VALUE; i++) { + if (this.check(i)) { + yield i; + } + } + } + + public toJSON(): any { + let isEmpty = true; + let isFull = true; + for (let i = 0; i < SIZE; i++) { + if (this.words[i] !== 0) { + isEmpty = false; + } + if (this.words[i] !== WORD_FILL) { + isFull = false; + } + } + if (isEmpty) { + return 'empty'; + } + if (isFull) { + return 'any'; + } + return Array.from(this); + } + + // Private + + private add(bit: number): void { + assert(0 <= bit && bit < MAX_VALUE, 'Invalid bit'); + const index = (bit / WORD_SIZE) | 0; + const off = bit % WORD_SIZE; + this.words[index] |= 1 << off; + } +} diff --git a/llparse-builder/src/node/base.ts b/llparse-builder/src/node/base.ts new file mode 100644 index 0000000..9840f16 --- /dev/null +++ b/llparse-builder/src/node/base.ts @@ -0,0 +1,96 @@ +import * as assert from 'assert'; +import binarySearch = require('binary-search'); +import { Edge } from '../edge'; + +/** + * Base class for all graph nodes. + */ +export abstract class Node { + private otherwiseEdge: Edge | undefined; + private privEdges: Edge[] = []; + + /** + * @param name Node name + */ + constructor(public readonly name: string) { + // no-op + } + + /** + * Create an otherwise edge to node `node`. + * + * This edge is executed when no other edges match current input. No + * characters are consumed upon transition. + * + * NOTE: At most one otherwise (skipping or not) edge can be set, most nodes + * except `Error` require it. + * + * @param node Target node + */ + public otherwise(node: Node): this { + if (this.otherwiseEdge !== undefined) { + throw new Error('Node already has `otherwise` or `skipTo`'); + } + + this.otherwiseEdge = new Edge(node, true, undefined, undefined); + return this; + } + + /** + * Create a skipping otherwise edge to node `node`. + * + * This edge is executed when no other edges match current input. Single + * character is consumed upon transition. + * + * NOTE: At most one otherwise (skipping or not) edge can be set, most nodes + * except `Error` require it. + * + * @param node Target node + */ + public skipTo(node: Node): this { + if (this.otherwiseEdge !== undefined) { + throw new Error('Node already has `otherwise` or `skipTo`'); + } + + this.otherwiseEdge = new Edge(node, false, undefined, undefined); + return this; + } + + // Limited public use + + /** Get otherwise edge. */ + public getOtherwiseEdge(): Edge | undefined { + return this.otherwiseEdge; + } + + /** Get list of all non-otherwise edges. */ + public getEdges(): ReadonlyArray<Edge> { + return this.privEdges; + } + + /** Get list of all edges (including otherwise, if present). */ + public getAllEdges(): ReadonlyArray<Edge> { + const res = this.privEdges; + if (this.otherwiseEdge === undefined) { + return res; + } else { + return res.concat(this.otherwiseEdge); + } + } + + /** Get iterator through all non-otherwise edges. */ + public *[Symbol.iterator](): Iterator<Edge> { + yield* this.privEdges; + } + + // Internal + + protected addEdge(edge: Edge): void { + assert.notStrictEqual(edge.key, undefined); + + const index = binarySearch(this.privEdges, edge, Edge.compare); + assert(index < 0, 'Attempting to create duplicate edge'); + + this.privEdges.splice(-1 - index, 0, edge); + } +} diff --git a/llparse-builder/src/node/consume.ts b/llparse-builder/src/node/consume.ts new file mode 100644 index 0000000..eff4037 --- /dev/null +++ b/llparse-builder/src/node/consume.ts @@ -0,0 +1,19 @@ +import * as assert from 'assert'; +import { Node } from './base'; + +/** + * This node consumes number of characters specified by state's property with + * name `field` from the input, and forwards execution to `otherwise` node. + */ +export class Consume extends Node { + /** + * @param field State's property name + */ + constructor(public readonly field: string) { + super('consume_' + field); + + if (/^_/.test(field)) { + throw new Error(`Can't use internal field in \`consume()\`: "${field}"`); + } + } +} diff --git a/llparse-builder/src/node/error.ts b/llparse-builder/src/node/error.ts new file mode 100644 index 0000000..393f566 --- /dev/null +++ b/llparse-builder/src/node/error.ts @@ -0,0 +1,24 @@ +import * as assert from 'assert'; +import { Node } from './base'; + +/** + * This node terminates the execution with an error + */ +class NodeError extends Node { + /** + * @param code Error code to return to user + * @param reason Error description to store in parser's state + */ + constructor(public readonly code: number, public readonly reason: string) { + super('error'); + assert.strictEqual(code, code | 0, 'code must be integer'); + } + + /** `.otherwise()` is not supported on this type of node */ + public otherwise(node: Node): this { throw new Error('Not supported'); } + + /** `.skipTo()` is not supported on this type of node */ + public skipTo(node: Node): this { throw new Error('Not supported'); } +} + +export { NodeError as Error }; diff --git a/llparse-builder/src/node/index.ts b/llparse-builder/src/node/index.ts new file mode 100644 index 0000000..e3d5fe5 --- /dev/null +++ b/llparse-builder/src/node/index.ts @@ -0,0 +1,8 @@ +export { Node } from './base'; +export { Consume } from './consume'; +export { Error } from './error'; +export { Invoke, IInvokeMap } from './invoke'; +export { Match } from './match'; +export { Pause } from './pause'; +export { SpanStart } from './span-start'; +export { SpanEnd } from './span-end'; diff --git a/llparse-builder/src/node/invoke.ts b/llparse-builder/src/node/invoke.ts new file mode 100644 index 0000000..d6791a7 --- /dev/null +++ b/llparse-builder/src/node/invoke.ts @@ -0,0 +1,39 @@ +import * as assert from 'assert'; + +import { Code } from '../code'; +import { Edge } from '../edge'; +import { Node } from './base'; + +/** + * Map of return codes of the callback. Each key is a return code, + * value is the target node that must be executed upon getting such return code. + */ +export interface IInvokeMap { + readonly [key: number]: Node; +} + +/** + * This node invokes either external callback or intrinsic code and passes the + * execution to either a target from a `map` (if the return code matches one of + * registered in it), or to `otherwise` node. + */ +export class Invoke extends Node { + /** + * @param code External callback or intrinsic code. Can be created with + * `builder.code.*()` methods. + * @param map Map from callback return codes to target nodes + */ + constructor(public readonly code: Code, map: IInvokeMap) { + super('invoke_' + code.name); + + Object.keys(map).forEach((mapKey) => { + const numKey: number = parseInt(mapKey, 10); + const targetNode = map[numKey]!; + + assert.strictEqual(numKey, numKey | 0, + 'Invoke\'s map keys must be integers'); + + this.addEdge(new Edge(targetNode, true, numKey, undefined)); + }); + } +} diff --git a/llparse-builder/src/node/match.ts b/llparse-builder/src/node/match.ts new file mode 100644 index 0000000..617a659 --- /dev/null +++ b/llparse-builder/src/node/match.ts @@ -0,0 +1,162 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import { Edge } from '../edge'; +import { Transform } from '../transform'; +import { toBuffer } from '../utils'; +import { Node } from './base'; + +/** + * Character/sequence to match. + * + * May have following types: + * + * * `number` - for single character + * * `string` - for printable character sequence + * * `Buffer` - for raw byte sequence + */ +export type MatchSingleValue = string | number | Buffer; + +/** + * Convenience type for passing several characters/sequences to match methods. + */ +export type MatchValue = MatchSingleValue | ReadonlyArray<MatchSingleValue>; + +/** + * A map from characters/sequences to `.select()`'s values. Used for specifying + * the value to be passed to `.select()'`s targets. + */ +export interface IMatchSelect { + readonly [key: string]: number; +} + +/** + * This node matches characters/sequences and forwards the execution according + * to matched character with optional attached value (See `.select()`). + */ +export class Match extends Node { + private transformFn: Transform | undefined; + + /** + * Set character transformation function. + * + * @param transform Transformation to apply. Can be created with + * `builder.transform.*()` methods. + */ + public transform(transformFn: Transform): this { + this.transformFn = transformFn; + return this; + } + + /** + * Match sequence/character and forward execution to `next` on success, + * consuming matched bytes of the input. + * + * No value is attached on such execution forwarding, and the target node + * **must not** be an `Invoke` node with a callback expecting the value. + * + * @param value Sequence/character to be matched + * @param next Target node to be executed on success. + */ + public match(value: MatchValue, next: Node): this { + if (Array.isArray(value)) { + for (const subvalue of value) { + this.match(subvalue, next); + } + return this; + } + + const buffer = toBuffer(value as MatchSingleValue); + const edge = new Edge(next, false, buffer, undefined); + this.addEdge(edge); + return this; + } + + /** + * Match character and forward execution to `next` on success + * without consuming one byte of the input. + * + * No value is attached on such execution forwarding, and the target node + * **must not** be an `Invoke` with a callback expecting the value. + * + * @param value Character to be matched + * @param next Target node to be executed on success. + */ + public peek(value: MatchValue, next: Node): this { + if (Array.isArray(value)) { + for (const subvalue of value) { + this.peek(subvalue, next); + } + return this; + } + + const buffer = toBuffer(value as MatchSingleValue); + assert.strictEqual(buffer.length, 1, + '`.peek()` accepts only single character keys'); + + const edge = new Edge(next, true, buffer, undefined); + this.addEdge(edge); + return this; + } + + /** + * Match character/sequence and forward execution to `next` on success + * consumed matched bytes of the input. + * + * Value is attached on such execution forwarding, and the target node + * **must** be an `Invoke` with a callback expecting the value. + * + * Possible signatures: + * + * * `.select(key, value [, next ])` + * * `.select({ key: value } [, next])` + * + * @param keyOrMap Either a sequence to match, or a map from sequences to + * values + * @param valueOrNext Either an integer value to be forwarded to the target + * node, or an otherwise node + * @param next Convenience param. Same as calling `.otherwise(...)` + */ + public select(keyOrMap: MatchSingleValue | IMatchSelect, + valueOrNext?: number | Node, next?: Node): this { + // .select({ key: value, ... }, next) + if (typeof keyOrMap === 'object') { + assert(valueOrNext instanceof Node, + 'Invalid `next` argument of `.select()`'); + assert.strictEqual(next, undefined, + 'Invalid argument count of `.select()`'); + + const map: IMatchSelect = keyOrMap as IMatchSelect; + next = valueOrNext as Node | undefined; + + Object.keys(map).forEach((mapKey) => { + const numKey: number = mapKey as any; + + this.select(numKey, map[numKey]!, next); + }); + return this; + } + + // .select(key, value, next) + assert.strictEqual(typeof valueOrNext, 'number', + 'Invalid `value` argument of `.select()`'); + assert.notStrictEqual(next, undefined, + 'Invalid `next` argument of `.select()`'); + + const key = toBuffer(keyOrMap as MatchSingleValue); + const value = valueOrNext as number; + + const edge = new Edge(next!, false, key, value); + this.addEdge(edge); + return this; + } + + // Limited public use + + /** + * Get tranformation function + */ + public getTransform(): Transform | undefined { + return this.transformFn; + } +} diff --git a/llparse-builder/src/node/pause.ts b/llparse-builder/src/node/pause.ts new file mode 100644 index 0000000..2dcf5d1 --- /dev/null +++ b/llparse-builder/src/node/pause.ts @@ -0,0 +1,25 @@ +import * as assert from 'assert'; +import { Node } from './base'; + +/** + * This returns the specified error code, but makes the resumption to + * `otherwise` target possible. + */ +export class Pause extends Node { + /** + * @param code Error code to return + * @param reason Error description + */ + constructor(public readonly code: number, public readonly reason: string) { + super('pause'); + assert.strictEqual(code, code | 0, 'code must be integer'); + } + + /** + * `.skipTo()` is not supported on this type of node, please use + * `.otherwise()` + */ + public skipTo(node: Node): this { + throw new Error('Not supported, please use `pause.otherwise()`'); + } +} diff --git a/llparse-builder/src/node/span-end.ts b/llparse-builder/src/node/span-end.ts new file mode 100644 index 0000000..377cd73 --- /dev/null +++ b/llparse-builder/src/node/span-end.ts @@ -0,0 +1,19 @@ +import { Span } from '../span'; +import { Node } from './base'; + +/** + * Indicates span end. + * + * A callback will be invoked with all input data since the most recent of: + * + * * Span start invocation + * * Parser execution + */ +export class SpanEnd extends Node { + /** + * @param span Span instance + */ + constructor(public readonly span: Span) { + super(`span_end_${span.callback.name}`); + } +} diff --git a/llparse-builder/src/node/span-start.ts b/llparse-builder/src/node/span-start.ts new file mode 100644 index 0000000..f81b432 --- /dev/null +++ b/llparse-builder/src/node/span-start.ts @@ -0,0 +1,16 @@ +import { Span } from '../span'; +import { Node } from './base'; + +/** + * Indicates span start. + * + * See `SpanEnd` for details on callback invocation. + */ +export class SpanStart extends Node { + /** + * @param span Span instance + */ + constructor(public readonly span: Span) { + super(`span_start_${span.callback.name}`); + } +} diff --git a/llparse-builder/src/property.ts b/llparse-builder/src/property.ts new file mode 100644 index 0000000..cf2fe4b --- /dev/null +++ b/llparse-builder/src/property.ts @@ -0,0 +1,12 @@ +export type PropertyType = 'i8' | 'i16' | 'i32' | 'i64' | 'ptr'; + +/** + * Class describing allocated property in parser's state + */ +export class Property { + constructor(public readonly ty: PropertyType, public readonly name: string) { + if (/^_/.test(name)) { + throw new Error(`Can't use internal property name: "${name}"`); + } + } +} diff --git a/llparse-builder/src/reachability.ts b/llparse-builder/src/reachability.ts new file mode 100644 index 0000000..88bcd65 --- /dev/null +++ b/llparse-builder/src/reachability.ts @@ -0,0 +1,31 @@ +import { Node } from './node'; + +/** + * This class finds all reachable nodes + */ +export class Reachability { + /** + * Build and return list of reachable nodes. + */ + public build(root: Node): ReadonlyArray<Node> { + const res = new Set(); + const queue = [ root ]; + while (queue.length !== 0) { + const node = queue.pop()!; + if (res.has(node)) { + continue; + } + res.add(node); + + for (const edge of node) { + queue.push(edge.node); + } + + const otherwise = node.getOtherwiseEdge(); + if (otherwise !== undefined) { + queue.push(otherwise.node); + } + } + return Array.from(res) as ReadonlyArray<Node>; + } +} diff --git a/llparse-builder/src/span-allocator.ts b/llparse-builder/src/span-allocator.ts new file mode 100644 index 0000000..b3e8f6b --- /dev/null +++ b/llparse-builder/src/span-allocator.ts @@ -0,0 +1,182 @@ +import * as assert from 'assert'; +import * as debugAPI from 'debug'; + +import { Node, SpanEnd, SpanStart } from './node'; +import { Reachability } from './reachability'; +import { Span } from './span'; + +const debug = debugAPI('llparse-builder:span-allocator'); + +type SpanSet = Set<Span>; + +interface ISpanActiveInfo { + readonly active: Map<Node, SpanSet>; + readonly spans: ReadonlyArray<Span>; +} + +type SpanOverlap = Map<Span, SpanSet>; + +export interface ISpanAllocatorResult { + readonly colors: ReadonlyMap<Span, number>; + readonly concurrency: ReadonlyArray<ReadonlyArray<Span> >; + readonly max: number; +} + +function id(node: SpanStart | SpanEnd): Span { + return node.span; +} + +export class SpanAllocator { + public allocate(root: Node): ISpanAllocatorResult { + const r = new Reachability(); + const nodes = r.build(root); + const info = this.computeActive(nodes); + this.check(info); + const overlap = this.computeOverlap(info); + return this.color(info.spans, overlap); + } + + private computeActive(nodes: ReadonlyArray<Node>): ISpanActiveInfo { + const activeMap: Map<Node, SpanSet> = new Map(); + nodes.forEach((node) => activeMap.set(node, new Set())); + + const queue: Set<Node> = new Set(nodes); + const spans: SpanSet = new Set(); + for (const node of queue) { + queue.delete(node); + + const active = activeMap.get(node)!; + + if (node instanceof SpanStart) { + const span = id(node); + spans.add(span); + active.add(span); + } + + active.forEach((span) => { + // Don't propagate span past the spanEnd + if (node instanceof SpanEnd && span === id(node)) { + return; + } + + node.getAllEdges().forEach((edge) => { + const edgeNode = edge.node; + + // Disallow loops + if (edgeNode instanceof SpanStart) { + assert.notStrictEqual(id(edgeNode), span, + `Detected loop in span "${span.callback.name}", started ` + + `at "${node.name}"`); + } + + const edgeActive = activeMap.get(edgeNode)!; + if (edgeActive.has(span)) { + return; + } + + edgeActive.add(span); + queue.add(edgeNode); + }); + }); + } + + return { active: activeMap, spans: Array.from(spans) }; + } + + private check(info: ISpanActiveInfo): void { + debug('check start'); + for (const [ node, spans ] of info.active) { + for (const edge of node.getAllEdges()) { + if (edge.node instanceof SpanStart) { + continue; + } + + // Skip terminal nodes + if (edge.node.getAllEdges().length === 0) { + continue; + } + + debug('checking edge from %j to %j', node.name, edge.node.name); + + const edgeSpans = info.active.get(edge.node)!; + for (const subSpan of edgeSpans) { + assert(spans.has(subSpan), + `Unmatched span end for "${subSpan.callback.name}" ` + + `at "${edge.node.name}", coming from "${node.name}"`); + } + + if (edge.node instanceof SpanEnd) { + const span = id(edge.node); + assert(spans.has(span), + `Unmatched span end for "${span.callback.name}"`); + } + } + } + } + + private computeOverlap(info: ISpanActiveInfo): SpanOverlap { + const active = info.active; + const overlap: SpanOverlap = new Map(); + + info.spans.forEach((span) => overlap.set(span, new Set())); + + active.forEach((spans) => { + spans.forEach((one) => { + const set = overlap.get(one)!; + spans.forEach((other) => { + if (other !== one) { + set.add(other); + } + }); + }); + }); + + return overlap; + } + + private color(spans: ReadonlyArray<Span>, overlapMap: SpanOverlap) + : ISpanAllocatorResult { + let max = -1; + const colors: Map<Span, number> = new Map(); + + const allocate = (span: Span): number => { + if (colors.has(span)) { + return colors.get(span)!; + } + + const overlap = overlapMap.get(span)!; + + // See which colors are already used + const used: Set<number> = new Set(); + for (const subSpan of overlap) { + if (colors.has(subSpan)) { + used.add(colors.get(subSpan)!); + } + } + + // Find minimum available color + let i; + for (i = 0; used.has(i); i++) { + // no-op + } + + max = Math.max(max, i); + colors.set(span, i); + + return i; + }; + + const map: Map<Span, number> = new Map(); + + spans.forEach((span) => map.set(span, allocate(span))); + + const concurrency: Span[][] = new Array(max + 1); + for (let i = 0; i < concurrency.length; i++) { + concurrency[i] = []; + } + + spans.forEach((span) => concurrency[allocate(span)].push(span)); + + return { colors: map, concurrency, max }; + } +} diff --git a/llparse-builder/src/span.ts b/llparse-builder/src/span.ts new file mode 100644 index 0000000..99cafb0 --- /dev/null +++ b/llparse-builder/src/span.ts @@ -0,0 +1,57 @@ +import * as assert from 'assert'; + +import { Span as SpanCallback } from './code'; +import { Node, SpanEnd, SpanStart } from './node'; + +/** + * Spans are used for notifying parser user about matched data. Each byte after + * span start will be sent to the span callback until span end is called. + */ +export class Span { + private readonly startCache: Map<Node, SpanStart> = new Map(); + private readonly endCache: Map<Node, SpanEnd> = new Map(); + + /** + * @param callback External callback, must be `code.span(...)` result. + */ + constructor(public readonly callback: SpanCallback) { + } + + /** + * Create `SpanStart` that indicates the start of the span. + * + * @param otherwise Optional convenience value. Same as calling + * `span.start().otherwise(...)` + */ + public start(otherwise?: Node) { + if (otherwise !== undefined && this.startCache.has(otherwise)) { + return this.startCache.get(otherwise)!; + } + + const res = new SpanStart(this); + if (otherwise !== undefined) { + res.otherwise(otherwise); + this.startCache.set(otherwise, res); + } + return res; + } + + /** + * Create `SpanEnd` that indicates the end of the span. + * + * @param otherwise Optional convenience value. Same as calling + * `span.end().otherwise(...)` + */ + public end(otherwise?: Node) { + if (otherwise !== undefined && this.endCache.has(otherwise)) { + return this.endCache.get(otherwise)!; + } + + const res = new SpanEnd(this); + if (otherwise !== undefined) { + res.otherwise(otherwise); + this.endCache.set(otherwise, res); + } + return res; + } +} diff --git a/llparse-builder/src/transform/base.ts b/llparse-builder/src/transform/base.ts new file mode 100644 index 0000000..902199c --- /dev/null +++ b/llparse-builder/src/transform/base.ts @@ -0,0 +1,12 @@ +export type TransformName = 'to_lower_unsafe' | 'to_lower'; + +/** + * Character transformation. + */ +export abstract class Transform { + /** + * @param name Transform name + */ + constructor(public readonly name: TransformName) { + } +} diff --git a/llparse-builder/src/transform/creator.ts b/llparse-builder/src/transform/creator.ts new file mode 100644 index 0000000..eaf3d5c --- /dev/null +++ b/llparse-builder/src/transform/creator.ts @@ -0,0 +1,28 @@ +import { Transform } from './base'; +import { ToLower } from './to-lower'; +import { ToLowerUnsafe } from './to-lower-unsafe'; + +/** + * API for creating character transformations. + * + * The results of methods of this class can be used as an argument to: + * `p.node().transform(...)`. + */ +export class Creator { + /** + * Unsafe transform to lowercase. + * + * The operation of this transformation is equivalent to: + * `String.fromCharCode(input.charCodeAt(0) | 0x20)`. + */ + public toLowerUnsafe(): Transform { + return new ToLowerUnsafe(); + } + + /** + * Safe transform to lowercase. + */ + public toLower(): Transform { + return new ToLower(); + } +} diff --git a/llparse-builder/src/transform/index.ts b/llparse-builder/src/transform/index.ts new file mode 100644 index 0000000..acdcf01 --- /dev/null +++ b/llparse-builder/src/transform/index.ts @@ -0,0 +1,3 @@ +export { Transform } from './base'; +export { Creator } from './creator'; +export { ToLowerUnsafe } from './to-lower-unsafe'; diff --git a/llparse-builder/src/transform/to-lower-unsafe.ts b/llparse-builder/src/transform/to-lower-unsafe.ts new file mode 100644 index 0000000..99d9618 --- /dev/null +++ b/llparse-builder/src/transform/to-lower-unsafe.ts @@ -0,0 +1,7 @@ +import { Transform } from './base'; + +export class ToLowerUnsafe extends Transform { + constructor() { + super('to_lower_unsafe'); + } +} diff --git a/llparse-builder/src/transform/to-lower.ts b/llparse-builder/src/transform/to-lower.ts new file mode 100644 index 0000000..b333fce --- /dev/null +++ b/llparse-builder/src/transform/to-lower.ts @@ -0,0 +1,7 @@ +import { Transform } from './base'; + +export class ToLower extends Transform { + constructor() { + super('to_lower'); + } +} diff --git a/llparse-builder/src/utils.ts b/llparse-builder/src/utils.ts new file mode 100644 index 0000000..3521b20 --- /dev/null +++ b/llparse-builder/src/utils.ts @@ -0,0 +1,19 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +/** + * Internal + */ +export function toBuffer(value: number | string | Buffer): Buffer { + let res: Buffer; + if (Buffer.isBuffer(value)) { + res = value; + } else if (typeof value === 'string') { + res = Buffer.from(value); + } else { + assert(0 <= value && value <= 0xff, 'Invalid byte value'); + res = Buffer.from([ value ]); + } + assert(res.length >= 1, 'Invalid key length'); + return res; +} diff --git a/llparse-builder/test/builder-test.ts b/llparse-builder/test/builder-test.ts new file mode 100644 index 0000000..82723ec --- /dev/null +++ b/llparse-builder/test/builder-test.ts @@ -0,0 +1,94 @@ +import * as assert from 'assert'; + +import { Builder } from '../src/builder'; + +describe('LLParse/Builder', () => { + let b: Builder; + beforeEach(() => { + b = new Builder(); + }); + + it('should build primitive graph', () => { + const start = b.node('start'); + const end = b.node('end'); + + start + .peek('e', end) + .match('a', start) + .otherwise(b.error(1, 'error')); + + end + .skipTo(start); + + const edges = start.getEdges(); + assert.strictEqual(edges.length, 2); + + assert(!edges[0].noAdvance); + assert.strictEqual(edges[0].node, start); + + assert(edges[1].noAdvance); + assert.strictEqual(edges[1].node, end); + }); + + it('should disallow duplicate edges', () => { + const start = b.node('start'); + + start.peek('e', start); + + assert.throws(() => { + start.peek('e', start); + }, /duplicate edge/); + }); + + it('should disallow select to non-invoke', () => { + const start = b.node('start'); + + assert.throws(() => { + start.select('a', 1, start); + }, /value to non-Invoke/); + }); + + it('should disallow select to match-invoke', () => { + const start = b.node('start'); + const invoke = b.invoke(b.code.match('something')); + + assert.throws(() => { + start.select('a', 1, invoke); + }, /Invalid.*code signature/); + }); + + it('should disallow peek to value-invoke', () => { + const start = b.node('start'); + const invoke = b.invoke(b.code.value('something')); + + assert.throws(() => { + start.peek('a', invoke); + }, /Invalid.*code signature/); + }); + + it('should allow select to value-invoke', () => { + const start = b.node('start'); + const invoke = b.invoke(b.code.value('something')); + + assert.doesNotThrow(() => { + start.select('a', 1, invoke); + }); + }); + + it('should create edges for Invoke', () => { + const start = b.node('start'); + const invoke = b.invoke(b.code.value('something'), { + '-1': start, + '1': start, + '10': start, + }); + + const edges = invoke.getEdges(); + const keys = edges.map((edge) => edge.key!); + assert.deepStrictEqual(keys, [ + -1, + 1, + 10, + ]); + }); +}); diff --git a/llparse-builder/test/loop-checker-test.ts b/llparse-builder/test/loop-checker-test.ts new file mode 100644 index 0000000..0df6064 --- /dev/null +++ b/llparse-builder/test/loop-checker-test.ts @@ -0,0 +1,118 @@ +import * as assert from 'assert'; + +import { Builder, LoopChecker } from '../src/builder'; + +describe('LLParse/LoopChecker', () => { + let b: Builder; + let lc: LoopChecker; + beforeEach(() => { + b = new Builder(); + lc = new LoopChecker(); + }); + + it('should detect shallow loops', () => { + const start = b.node('start'); + + start + .otherwise(start); + + assert.throws(() => { + lc.check(start); + }, /Detected loop in "start".*"start"/); + }); + + it('should detect loops', () => { + const start = b.node('start'); + const a = b.node('a'); + const invoke = b.invoke(b.code.match('nop'), { + 0: start, + }, b.error(1, 'error')); + + start + .peek('a', a) + .otherwise(b.error(1, 'error')); + + a.otherwise(invoke); + + assert.throws(() => { + lc.check(start); + }, /Detected loop in "a".*"a" -> "invoke_nop"/); + }); + + it('should detect seemingly unreachable keys', () => { + const start = b.node('start'); + const loop = b.node('loop'); + + start + .peek('a', loop) + .otherwise(b.error(1, 'error')); + + loop + .match('a', loop) + .otherwise(loop); + + assert.throws(() => { + lc.check(start); + }, /Detected loop in "loop" through.*"loop"/); + }); + + it('should ignore loops through `peek` to `match`', () => { + const start = b.node('start'); + const a = b.node('a'); + const invoke = b.invoke(b.code.match('nop'), { + 0: start, + }, b.error(1, 'error')); + + start + .peek('a', a) + .otherwise(b.error(1, 'error')); + + a + .match('abc', invoke) + .otherwise(start); + + assert.doesNotThrow(() => lc.check(start)); + }); + + it('should ignore irrelevant `peek`s', () => { + const start = b.node('start'); + const a = b.node('a'); + + start + .peek('a', a) + .otherwise(b.error(1, 'error')); + + a + .peek('b', start) + .otherwise(b.error(1, 'error')); + + assert.doesNotThrow(() => lc.check(start)); + }); + + it('should ignore loops with multi `peek`/`match`', () => { + const start = b.node('start'); + const another = b.node('another'); + + const NUM: ReadonlyArray<string> = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ]; + + const ALPHA: ReadonlyArray<string> = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + ]; + + start + .match(ALPHA, start) + .peek(NUM, another) + .skipTo(start); + + another + .match(NUM, another) + .otherwise(start); + + assert.doesNotThrow(() => lc.check(start)); + }); +}); diff --git a/llparse-builder/test/span-allocator-test.ts b/llparse-builder/test/span-allocator-test.ts new file mode 100644 index 0000000..bc8f656 --- /dev/null +++ b/llparse-builder/test/span-allocator-test.ts @@ -0,0 +1,146 @@ +import * as assert from 'assert'; + +import { Builder, SpanAllocator } from '../src/builder'; + +describe('LLParse/LoopChecker', () => { + let b: Builder; + let sa: SpanAllocator; + beforeEach(() => { + b = new Builder(); + sa = new SpanAllocator(); + }); + + it('should allocate single span', () => { + const span = b.span(b.code.span('span')); + const start = b.node('start'); + const body = b.node('body'); + + start + .otherwise(span.start(body)); + + body + .skipTo(span.end(start)); + + const res = sa.allocate(start); + + assert.strictEqual(res.max, 0); + + assert.strictEqual(res.concurrency.length, 1); + assert.ok(res.concurrency[0].includes(span)); + + assert.strictEqual(res.colors.size, 1); + assert.strictEqual(res.colors.get(span), 0); + }); + + it('should allocate overlapping spans', () => { + const span1 = b.span(b.code.span('span1')); + const span2 = b.span(b.code.span('span2')); + + const start = b.node('start'); + const body1 = b.node('body1'); + const body2 = b.node('body2'); + + start + .otherwise(span1.start(body1)); + + body1 + .otherwise(span2.start(body2)); + + body2 + .skipTo(span2.end(span1.end(start))); + + const res = sa.allocate(start); + + assert.strictEqual(res.max, 1); + + assert.strictEqual(res.concurrency.length, 2); + assert.ok(res.concurrency[0].includes(span1)); + assert.ok(res.concurrency[1].includes(span2)); + + assert.strictEqual(res.colors.size, 2); + assert.strictEqual(res.colors.get(span1), 0); + assert.strictEqual(res.colors.get(span2), 1); + }); + + it('should allocate non-overlapping spans', () => { + const span1 = b.span(b.code.span('span1')); + const span2 = b.span(b.code.span('span2')); + + const start = b.node('start'); + const body1 = b.node('body1'); + const body2 = b.node('body2'); + + start + .match('a', span1.start(body1)) + .otherwise(span2.start(body2)); + + body1 + .skipTo(span1.end(start)); + + body2 + .skipTo(span2.end(start)); + + const res = sa.allocate(start); + + assert.strictEqual(res.max, 0); + + assert.strictEqual(res.concurrency.length, 1); + assert.ok(res.concurrency[0].includes(span1)); + assert.ok(res.concurrency[0].includes(span2)); + + assert.strictEqual(res.colors.size, 2); + assert.strictEqual(res.colors.get(span1), 0); + assert.strictEqual(res.colors.get(span2), 0); + }); + + it('should throw on loops', () => { + const span = b.span(b.code.span('span_name')); + + const start = b.node('start'); + + start + .otherwise(span.start(start)); + + assert.throws(() => { + sa.allocate(start); + }, /loop.*span_name/); + }); + + it('should throw on unmatched ends', () => { + const start = b.node('start'); + const span = b.span(b.code.span('on_data')); + + start.otherwise(span.end().skipTo(start)); + + assert.throws(() => sa.allocate(start), /unmatched.*on_data/i); + }); + + it('should throw on branched unmatched ends', () => { + const start = b.node('start'); + const end = b.node('end'); + const span = b.span(b.code.span('on_data')); + + start + .match('a', end) + .match('b', span.start(end)) + .otherwise(b.error(1, 'error')); + + end + .otherwise(span.end(start)); + + assert.throws(() => sa.allocate(start), /unmatched.*on_data/i); + }); + + it('should propagate through the Invoke map', () => { + const start = b.node('start'); + const span = b.span(b.code.span('llparse__on_data')); + + b.property('i8', 'custom'); + + start.otherwise(b.invoke(b.code.load('custom'), { + 0: span.end().skipTo(start), + }, span.end().skipTo(start))); + + assert.doesNotThrow(() => sa.allocate(span.start(start))); + }); +}); diff --git a/llparse-builder/tsconfig.json b/llparse-builder/tsconfig.json new file mode 100644 index 0000000..01ec7c2 --- /dev/null +++ b/llparse-builder/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2017", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./lib", + "declaration": true, + "pretty": true, + "sourceMap": true + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/llparse-builder/tslint.json b/llparse-builder/tslint.json new file mode 100644 index 0000000..b0aaf97 --- /dev/null +++ b/llparse-builder/tslint.json @@ -0,0 +1,14 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-bitwise": null, + "quotemark": [ + true, "single", "avoid-escape", "avoid-template" + ] + }, + "rulesDirectory": [] +} diff --git a/llparse-frontend/.gitignore b/llparse-frontend/.gitignore new file mode 100644 index 0000000..88edb62 --- /dev/null +++ b/llparse-frontend/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +lib/ diff --git a/llparse-frontend/.travis.yml b/llparse-frontend/.travis.yml new file mode 100644 index 0000000..03f4af5 --- /dev/null +++ b/llparse-frontend/.travis.yml @@ -0,0 +1,6 @@ +sudo: false +language: node_js +node_js: + - "stable" +script: + npm test diff --git a/llparse-frontend/README.md b/llparse-frontend/README.md new file mode 100644 index 0000000..359dd9b --- /dev/null +++ b/llparse-frontend/README.md @@ -0,0 +1,30 @@ +# llparse-frontend +[![Build Status](https://secure.travis-ci.org/indutny/llparse-frontend.svg)](http://travis-ci.org/indutny/llparse-frontend) +[![NPM version](https://badge.fury.io/js/llparse-frontend.svg)](https://badge.fury.io/js/llparse-frontend) + +WIP + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/llparse-frontend/package-lock.json b/llparse-frontend/package-lock.json new file mode 100644 index 0000000..3cfef7a --- /dev/null +++ b/llparse-frontend/package-lock.json @@ -0,0 +1,1516 @@ +{ + "name": "llparse-frontend", + "version": "3.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz", + "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz", + "integrity": "sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", + "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.3", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/node": { + "version": "14.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", + "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "binary-search": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", + "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + } + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "llparse-builder": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz", + "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==", + "requires": { + "@types/debug": "4.1.5 ", + "binary-search": "^1.3.6", + "debug": "^4.2.0" + }, + "dependencies": { + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.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==", + "dev": true, + "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==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "mocha": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", + "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.2", + "debug": "4.1.1", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "4.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", + "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "decamelize": "^1.2.0", + "flat": "^4.1.0", + "is-plain-obj": "^1.1.0", + "yargs": "^14.2.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/llparse-frontend/package.json b/llparse-frontend/package.json new file mode 100644 index 0000000..8afea88 --- /dev/null +++ b/llparse-frontend/package.json @@ -0,0 +1,43 @@ +{ + "name": "llparse-frontend", + "version": "3.0.0", + "description": "Frontend for LLParse compiler", + "main": "lib/frontend.js", + "types": "lib/frontend.d.ts", + "scripts": { + "build": "tsc", + "clean": "rm -rf lib", + "prepare": "npm run clean && npm run build", + "lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", + "fix-lint": "npm run lint -- --fix", + "mocha": "mocha --timeout=10000 -r ts-node/register/type-check --reporter spec test/*-test.ts", + "test": "npm run mocha && npm run lint" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/indutny/llparse-frontend.git" + }, + "keywords": [ + "llparse", + "frontend" + ], + "author": "Fedor Indutny <fedor@indutny.com> (http://darksi.de/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/indutny/llparse-frontend/issues" + }, + "homepage": "https://github.com/indutny/llparse-frontend#readme", + "dependencies": { + "debug": "^3.2.6", + "llparse-builder": "^1.5.2" + }, + "devDependencies": { + "@types/debug": "^4.1.5", + "@types/mocha": "^8.0.3", + "@types/node": "^14.11.8", + "mocha": "^8.1.3", + "ts-node": "^9.0.0", + "tslint": "^5.20.1", + "typescript": "^4.0.3" + } +} diff --git a/llparse-frontend/src/code/and.ts b/llparse-frontend/src/code/and.ts new file mode 100644 index 0000000..54dc5fd --- /dev/null +++ b/llparse-frontend/src/code/and.ts @@ -0,0 +1,8 @@ +import { toCacheKey } from '../utils'; +import { FieldValue } from './field-value'; + +export class And extends FieldValue { + constructor(name: string, field: string, value: number) { + super('match', `and_${field}_${toCacheKey(value)}`, name, field, value); + } +} diff --git a/llparse-frontend/src/code/base.ts b/llparse-frontend/src/code/base.ts new file mode 100644 index 0000000..cde4b6d --- /dev/null +++ b/llparse-frontend/src/code/base.ts @@ -0,0 +1,8 @@ +export type Signature = 'match' | 'value' | 'span'; + +export abstract class Code { + constructor(public readonly signature: Signature, + public readonly cacheKey: string, + public readonly name: string) { + } +} diff --git a/llparse-frontend/src/code/external.ts b/llparse-frontend/src/code/external.ts new file mode 100644 index 0000000..f4254c1 --- /dev/null +++ b/llparse-frontend/src/code/external.ts @@ -0,0 +1,7 @@ +import { Code, Signature } from './base'; + +export abstract class External extends Code { + constructor(signature: Signature, name: string) { + super(signature, 'external_' + name, name); + } +} diff --git a/llparse-frontend/src/code/field-value.ts b/llparse-frontend/src/code/field-value.ts new file mode 100644 index 0000000..1c7c109 --- /dev/null +++ b/llparse-frontend/src/code/field-value.ts @@ -0,0 +1,13 @@ +import * as assert from 'assert'; + +import { Signature } from './base'; +import { Field } from './field'; + +export abstract class FieldValue extends Field { + constructor(signature: Signature, cacheKey: string, name: string, + field: string, public readonly value: number) { + super(signature, cacheKey, name, field); + + assert.strictEqual(value, value | 0, 'FieldValue `value` must be integer'); + } +} diff --git a/llparse-frontend/src/code/field.ts b/llparse-frontend/src/code/field.ts new file mode 100644 index 0000000..c60b8ef --- /dev/null +++ b/llparse-frontend/src/code/field.ts @@ -0,0 +1,8 @@ +import { Code, Signature } from './base'; + +export abstract class Field extends Code { + constructor(signature: Signature, cacheKey: string, name: string, + public readonly field: string) { + super(signature, cacheKey, name); + } +} diff --git a/llparse-frontend/src/code/index.ts b/llparse-frontend/src/code/index.ts new file mode 100644 index 0000000..c7d5c69 --- /dev/null +++ b/llparse-frontend/src/code/index.ts @@ -0,0 +1,15 @@ +export * from './and'; +export * from './base'; +export * from './external'; +export * from './field-value'; +export * from './field'; +export * from './is-equal'; +export * from './load'; +export * from './match'; +export * from './mul-add'; +export * from './or'; +export * from './span'; +export * from './store'; +export * from './test'; +export * from './update'; +export * from './value'; diff --git a/llparse-frontend/src/code/is-equal.ts b/llparse-frontend/src/code/is-equal.ts new file mode 100644 index 0000000..16a2ee2 --- /dev/null +++ b/llparse-frontend/src/code/is-equal.ts @@ -0,0 +1,9 @@ +import { toCacheKey } from '../utils'; +import { FieldValue } from './field-value'; + +export class IsEqual extends FieldValue { + constructor(name: string, field: string, value: number) { + super('match', `is_equal_${field}_${toCacheKey(value)}`, name, field, + value); + } +} diff --git a/llparse-frontend/src/code/load.ts b/llparse-frontend/src/code/load.ts new file mode 100644 index 0000000..76b715a --- /dev/null +++ b/llparse-frontend/src/code/load.ts @@ -0,0 +1,7 @@ +import { Field } from './field'; + +export class Load extends Field { + constructor(name: string, field: string) { + super('match', `load_${field}`, name, field); + } +} diff --git a/llparse-frontend/src/code/match.ts b/llparse-frontend/src/code/match.ts new file mode 100644 index 0000000..819d2af --- /dev/null +++ b/llparse-frontend/src/code/match.ts @@ -0,0 +1,7 @@ +import { External } from './external'; + +export class Match extends External { + constructor(name: string) { + super('match', name); + } +} diff --git a/llparse-frontend/src/code/mul-add.ts b/llparse-frontend/src/code/mul-add.ts new file mode 100644 index 0000000..c99be0d --- /dev/null +++ b/llparse-frontend/src/code/mul-add.ts @@ -0,0 +1,26 @@ +import { toCacheKey } from '../utils'; +import { Field } from './field'; + +export interface IMulAddOptions { + readonly base: number; + readonly max?: number; + readonly signed: boolean; +} + +function toOptionsKey(options: IMulAddOptions): string { + let res = `base_${toCacheKey(options.base)}`; + if (options.max !== undefined) { + res += `_max_${toCacheKey(options.max)}`; + } + if (options.signed !== undefined) { + res += `_signed_${toCacheKey(options.signed)}`; + } + return res; +} + +export class MulAdd extends Field { + constructor(name: string, field: string, + public readonly options: IMulAddOptions) { + super('value', `mul_add_${field}_${toOptionsKey(options)}`, name, field); + } +} diff --git a/llparse-frontend/src/code/or.ts b/llparse-frontend/src/code/or.ts new file mode 100644 index 0000000..2328a9f --- /dev/null +++ b/llparse-frontend/src/code/or.ts @@ -0,0 +1,8 @@ +import { toCacheKey } from '../utils'; +import { FieldValue } from './field-value'; + +export class Or extends FieldValue { + constructor(name: string, field: string, value: number) { + super('match', `or_${field}_${toCacheKey(value)}`, name, field, value); + } +} diff --git a/llparse-frontend/src/code/span.ts b/llparse-frontend/src/code/span.ts new file mode 100644 index 0000000..6241e03 --- /dev/null +++ b/llparse-frontend/src/code/span.ts @@ -0,0 +1,7 @@ +import { External } from './external'; + +export class Span extends External { + constructor(name: string) { + super('span', name); + } +} diff --git a/llparse-frontend/src/code/store.ts b/llparse-frontend/src/code/store.ts new file mode 100644 index 0000000..c2cb9ea --- /dev/null +++ b/llparse-frontend/src/code/store.ts @@ -0,0 +1,7 @@ +import { Field } from './field'; + +export class Store extends Field { + constructor(name: string, field: string) { + super('value', `store_${field}`, name, field); + } +} diff --git a/llparse-frontend/src/code/test.ts b/llparse-frontend/src/code/test.ts new file mode 100644 index 0000000..21339e9 --- /dev/null +++ b/llparse-frontend/src/code/test.ts @@ -0,0 +1,8 @@ +import { toCacheKey } from '../utils'; +import { FieldValue } from './field-value'; + +export class Test extends FieldValue { + constructor(name: string, field: string, value: number) { + super('match', `test_${field}_${toCacheKey(value)}`, name, field, value); + } +} diff --git a/llparse-frontend/src/code/update.ts b/llparse-frontend/src/code/update.ts new file mode 100644 index 0000000..5fa5eec --- /dev/null +++ b/llparse-frontend/src/code/update.ts @@ -0,0 +1,8 @@ +import { toCacheKey } from '../utils'; +import { FieldValue } from './field-value'; + +export class Update extends FieldValue { + constructor(name: string, field: string, value: number) { + super('match', `update_${field}_${toCacheKey(value)}`, name, field, value); + } +} diff --git a/llparse-frontend/src/code/value.ts b/llparse-frontend/src/code/value.ts new file mode 100644 index 0000000..4f32ae8 --- /dev/null +++ b/llparse-frontend/src/code/value.ts @@ -0,0 +1,7 @@ +import { External } from './external'; + +export class Value extends External { + constructor(name: string) { + super('value', name); + } +} diff --git a/llparse-frontend/src/container/index.ts b/llparse-frontend/src/container/index.ts new file mode 100644 index 0000000..a62aac8 --- /dev/null +++ b/llparse-frontend/src/container/index.ts @@ -0,0 +1,84 @@ +import * as assert from 'assert'; + +import { ICodeImplementation } from '../implementation/code'; +import { IImplementation } from '../implementation/full'; +import { INodeImplementation } from '../implementation/node'; +import { ITransformImplementation } from '../implementation/transform'; +import { IWrap } from '../wrap'; +import { ContainerWrap } from './wrap'; + +export { ContainerWrap }; + +export class Container { + private readonly map: Map<string, IImplementation> = new Map(); + + public add(key: string, impl: IImplementation): void { + assert(!this.map.has(key), `Duplicate implementation key: "${key}"`); + this.map.set(key, impl); + } + + public build(): IImplementation { + return { + code: this.buildCode(), + node: this.buildNode(), + transform: this.buildTransform(), + }; + } + + public buildCode(): ICodeImplementation { + return { + And: this.combine((impl) => impl.code.And), + IsEqual: this.combine((impl) => impl.code.IsEqual), + Load: this.combine((impl) => impl.code.Load), + Match: this.combine((impl) => impl.code.Match), + MulAdd: this.combine((impl) => impl.code.MulAdd), + Or: this.combine((impl) => impl.code.Or), + Span: this.combine((impl) => impl.code.Span), + Store: this.combine((impl) => impl.code.Store), + Test: this.combine((impl) => impl.code.Test), + Update: this.combine((impl) => impl.code.Update), + Value: this.combine((impl) => impl.code.Value), + }; + } + + public buildNode(): INodeImplementation { + return { + Consume: this.combine((impl) => impl.node.Consume), + Empty: this.combine((impl) => impl.node.Empty), + Error: this.combine((impl) => impl.node.Error), + Invoke: this.combine((impl) => impl.node.Invoke), + Pause: this.combine((impl) => impl.node.Pause), + Sequence: this.combine((impl) => impl.node.Sequence), + Single: this.combine((impl) => impl.node.Single), + SpanEnd: this.combine((impl) => impl.node.SpanEnd), + SpanStart: this.combine((impl) => impl.node.SpanStart), + TableLookup: this.combine((impl) => impl.node.TableLookup), + }; + } + + public buildTransform(): ITransformImplementation { + return { + ID: this.combine((impl) => impl.transform.ID), + ToLower: this.combine((impl) => impl.transform.ToLower), + ToLowerUnsafe: this.combine((impl) => impl.transform.ToLowerUnsafe), + }; + } + + private combine<T>(gather: (impl: IImplementation) => new(n: T) => IWrap<T>) + : new(n: T) => ContainerWrap<T> { + const wraps: Map<string, new(n: T) => IWrap<T>> = new Map(); + for (const [ key, impl ] of this.map) { + wraps.set(key, gather(impl)); + } + + return class ContainerWrapSingle extends ContainerWrap<T> { + constructor(ref: T) { + super(ref); + + for (const [ key, impl ] of wraps) { + this.map.set(key, new impl(ref)); + } + } + }; + } +} diff --git a/llparse-frontend/src/container/wrap.ts b/llparse-frontend/src/container/wrap.ts new file mode 100644 index 0000000..f3b886c --- /dev/null +++ b/llparse-frontend/src/container/wrap.ts @@ -0,0 +1,15 @@ +import * as assert from 'assert'; + +import { IWrap } from '../wrap'; + +export class ContainerWrap<T> { + protected readonly map: Map<string, IWrap<T>> = new Map(); + + constructor(public readonly ref: T) { + } + + public get<R extends IWrap<T>>(key: string): R { + assert(this.map.has(key), `Unknown implementation key "${key}"`); + return this.map.get(key)! as R; + } +} diff --git a/llparse-frontend/src/enumerator.ts b/llparse-frontend/src/enumerator.ts new file mode 100644 index 0000000..f2940a2 --- /dev/null +++ b/llparse-frontend/src/enumerator.ts @@ -0,0 +1,23 @@ +import { Node } from './node'; +import { IWrap } from './wrap'; + +export class Enumerator { + public getAllNodes(root: IWrap<Node>): ReadonlyArray<IWrap<Node>> { + const nodes: Set<IWrap<Node>> = new Set(); + const queue = [ root ]; + + while (queue.length !== 0) { + const node = queue.pop()!; + for (const slot of node.ref.getSlots()) { + if (nodes.has(slot.node)) { + continue; + } + + nodes.add(slot.node); + queue.push(slot.node); + } + } + + return Array.from(nodes); + } +} diff --git a/llparse-frontend/src/frontend.ts b/llparse-frontend/src/frontend.ts new file mode 100644 index 0000000..91c5224 --- /dev/null +++ b/llparse-frontend/src/frontend.ts @@ -0,0 +1,513 @@ +import * as assert from 'assert'; +import * as debugAPI from 'debug'; +import * as source from 'llparse-builder'; + +import * as frontend from './namespace/frontend'; +import { Container, ContainerWrap } from './container'; +import { IImplementation } from './implementation'; +import { SpanField } from './span-field'; +import { Trie, TrieEmpty, TrieNode, TrieSequence, TrieSingle } from './trie'; +import { Identifier, IUniqueName } from './utils'; +import { IWrap } from './wrap'; +import { Enumerator } from './enumerator'; +import { Peephole } from './peephole'; + +const debug = debugAPI('llparse:translator'); + +export { code, node, transform } from './namespace/frontend'; + +export { + source, + Identifier, + IUniqueName, + IWrap, + SpanField, + Container, + ContainerWrap, +}; + +// Minimum number of cases of `single` node to make it eligable for +// `TableLookup` optimization +export const DEFAULT_MIN_TABLE_SIZE = 32; + +// Maximum width of entry in a table for a `TableLookup` optimization +export const DEFAULT_MAX_TABLE_WIDTH = 4; + +type WrappedNode = IWrap<frontend.node.Node>; +type WrappedCode = IWrap<frontend.code.Code>; + +export interface IFrontendLazyOptions { + readonly maxTableElemWidth?: number; + readonly minTableSize?: number; +} + +export interface IFrontendResult { + readonly prefix: string; + readonly properties: ReadonlyArray<source.Property>; + readonly root: IWrap<frontend.node.Node>; + readonly spans: ReadonlyArray<SpanField>; + readonly resumptionTargets: ReadonlySet<WrappedNode>; +} + +interface IFrontendOptions { + readonly maxTableElemWidth: number; + readonly minTableSize: number; +} + +type MatchChildren = WrappedNode[]; +type MatchResult = WrappedNode | ReadonlyArray<WrappedNode>; + +interface ITableLookupTarget { + readonly keys: number[]; + readonly noAdvance: boolean; + readonly trie: TrieEmpty; +} + +export class Frontend { + private readonly options: IFrontendOptions; + + private readonly id: Identifier = new Identifier(this.prefix + '__n_'); + private readonly codeId: Identifier = new Identifier(this.prefix + '__c_'); + private readonly map: Map<source.node.Node, WrappedNode> = new Map(); + private readonly spanMap: Map<source.Span, SpanField> = new Map(); + private readonly codeCache: Map<string, WrappedCode> = new Map(); + private readonly resumptionTargets: Set<WrappedNode> = new Set(); + + constructor(private readonly prefix: string, + private readonly implementation: IImplementation, + options: IFrontendLazyOptions = {}) { + this.options = { + maxTableElemWidth: options.maxTableElemWidth === undefined ? + DEFAULT_MAX_TABLE_WIDTH : options.maxTableElemWidth, + minTableSize: options.minTableSize === undefined ? + DEFAULT_MIN_TABLE_SIZE : options.minTableSize, + }; + + assert(0 < this.options.maxTableElemWidth, + 'Invalid `options.maxTableElemWidth`, must be positive'); + } + + public compile(root: source.node.Node, + properties: ReadonlyArray<source.Property>): IFrontendResult { + debug('checking loops'); + const lc = new source.LoopChecker(); + lc.check(root); + + debug('allocating spans'); + const spanAllocator = new source.SpanAllocator(); + const sourceSpans = spanAllocator.allocate(root); + + const spans = sourceSpans.concurrency.map((concurrent, index) => { + const span = new SpanField(index, concurrent.map((sourceSpan) => { + return this.translateSpanCode(sourceSpan.callback); + })); + + for (const sourceSpan of concurrent) { + this.spanMap.set(sourceSpan, span); + } + + return span; + }); + + debug('translating'); + let out = this.translate(root); + + debug('enumerating'); + const enumerator = new Enumerator(); + let nodes = enumerator.getAllNodes(out); + + debug('peephole optimization'); + const peephole = new Peephole(); + out = peephole.optimize(out, nodes); + + debug('re-enumerating'); + nodes = enumerator.getAllNodes(out); + + debug('registering resumption targets'); + this.resumptionTargets.add(out); + for (const node of nodes) { + this.registerNode(node); + } + + return { + prefix: this.prefix, + properties, + resumptionTargets: this.resumptionTargets, + root: out, + spans, + }; + } + + // TODO(indutny): remove this in the next major release + public getResumptionTargets(): ReadonlySet<WrappedNode> { + return this.resumptionTargets; + } + + private translate(node: source.node.Node): WrappedNode { + if (this.map.has(node)) { + return this.map.get(node)!; + } + + const id = () => this.id.id(node.name); + + const nodeImpl = this.implementation.node; + + // Instantiate target class + let result: MatchResult; + if (node instanceof source.node.Error) { + result = new nodeImpl.Error( + new frontend.node.Error(id(), node.code, node.reason)); + } else if (node instanceof source.node.Pause) { + result = new nodeImpl.Pause( + new frontend.node.Pause(id(), node.code, node.reason)); + } else if (node instanceof source.node.Consume) { + result = new nodeImpl.Consume( + new frontend.node.Consume(id(), node.field)); + } else if (node instanceof source.node.SpanStart) { + result = new nodeImpl.SpanStart( + new frontend.node.SpanStart(id(), this.spanMap.get(node.span)!, + this.translateSpanCode(node.span.callback))); + } else if (node instanceof source.node.SpanEnd) { + result = new nodeImpl.SpanEnd( + new frontend.node.SpanEnd(id(), this.spanMap.get(node.span)!, + this.translateSpanCode(node.span.callback))); + } else if (node instanceof source.node.Invoke) { + assert(node.code.signature === 'match' || node.code.signature === 'value', + 'Passing `span` callback to `invoke` is not allowed'); + result = new nodeImpl.Invoke( + new frontend.node.Invoke(id(), this.translateCode(node.code))); + } else if (node instanceof source.node.Match) { + result = this.translateMatch(node); + } else { + throw new Error(`Unknown node type for "${node.name}" ${node.constructor.toString()}`); + } + + // Initialize result + const otherwise = node.getOtherwiseEdge(); + + if (Array.isArray(result)) { + assert(node instanceof source.node.Match); + const match = node as source.node.Match; + + // TODO(indutny): move this to llparse-builder? + assert.notStrictEqual(otherwise, undefined, + `Node "${node.name}" has no \`.otherwise()\``); + + // Assign otherwise to every node of Trie + if (otherwise !== undefined) { + for (const child of result) { + if (!child.ref.otherwise) { + child.ref.setOtherwise(this.translate(otherwise.node), + otherwise.noAdvance); + } + } + } + + // Assign transform to every node of Trie + const transform = this.translateTransform(match.getTransform()); + for (const child of result) { + child.ref.setTransform(transform); + } + + assert(result.length >= 1); + return result[0]; + } else { + const single: WrappedNode = result as WrappedNode; + assert(single.ref instanceof frontend.node.Node); + + // Break loops + this.map.set(node, single); + + if (otherwise !== undefined) { + single.ref.setOtherwise(this.translate(otherwise.node), + otherwise.noAdvance); + } else { + // TODO(indutny): move this to llparse-builder? + assert(node instanceof source.node.Error, + `Node "${node.name}" has no \`.otherwise()\``); + } + + if (single.ref instanceof frontend.node.Invoke) { + for (const edge of node) { + single.ref.addEdge(edge.key as number, this.translate(edge.node)); + } + } else { + assert.strictEqual(Array.from(node).length, 0); + } + + return single; + } + } + + private registerNode(node: any): void { + const nodeImpl = this.implementation.node; + + // Nodes with prologue check (start_pos != end_pos) + if (node instanceof nodeImpl.Consume || + node instanceof nodeImpl.Empty || + node instanceof nodeImpl.Sequence || + node instanceof nodeImpl.Single || + node instanceof nodeImpl.SpanStart || + node instanceof nodeImpl.TableLookup) { + this.resumptionTargets.add(node); + + // Nodes that can interrupt the execution to be resumed at different node + } else if (node instanceof nodeImpl.Pause || + node instanceof nodeImpl.SpanEnd) { + this.resumptionTargets.add(node.ref.otherwise!.node); + } + } + + private translateMatch(node: source.node.Match): MatchResult { + const trie = new Trie(node.name); + + const otherwise = node.getOtherwiseEdge(); + const trieNode = trie.build(Array.from(node)); + if (trieNode === undefined) { + return new this.implementation.node.Empty( + new frontend.node.Empty(this.id.id(node.name))); + } + + const children: MatchChildren = []; + this.translateTrie(node, trieNode, children); + assert(children.length >= 1); + + return children; + } + + private translateTrie(node: source.node.Match, trie: TrieNode, + children: MatchChildren): WrappedNode { + if (trie instanceof TrieEmpty) { + assert(this.map.has(node)); + return this.translate(trie.node); + } else if (trie instanceof TrieSingle) { + return this.translateSingle(node, trie, children); + } else if (trie instanceof TrieSequence) { + return this.translateSequence(node, trie, children); + } else { + throw new Error('Unknown trie node'); + } + } + + private translateSingle(node: source.node.Match, trie: TrieSingle, + children: MatchChildren) + : IWrap<frontend.node.Match> { + // See if we can apply TableLookup optimization + const maybeTable = this.maybeTableLookup(node, trie, children); + if (maybeTable !== undefined) { + return maybeTable; + } + + const single = new this.implementation.node.Single( + new frontend.node.Single(this.id.id(node.name))); + children.push(single); + + // Break the loop + if (!this.map.has(node)) { + this.map.set(node, single); + } + for (const child of trie.children) { + const childNode = this.translateTrie(node, child.node, children); + + single.ref.addEdge({ + key: child.key, + noAdvance: child.noAdvance, + node: childNode, + value: child.node instanceof TrieEmpty ? child.node.value : undefined, + }); + } + + const otherwise = trie.otherwise; + if (otherwise) { + single.ref.setOtherwise( + this.translateTrie(node, otherwise, children), + true, + otherwise.value); + } + + return single; + } + + private maybeTableLookup(node: source.node.Match, trie: TrieSingle, + children: MatchChildren) + : IWrap<frontend.node.Match> | undefined { + if (trie.children.length < this.options.minTableSize) { + debug('not enough children of "%s" to allocate table, got %d need %d', + node.name, trie.children.length, this.options.minTableSize); + return undefined; + } + + const targets: Map<source.node.Node, ITableLookupTarget> = new Map(); + + const bailout = !trie.children.every((child) => { + if (!(child.node instanceof TrieEmpty)) { + debug('non-leaf trie child of "%s" prevents table allocation', + node.name); + return false; + } + + const empty: TrieEmpty = child.node; + + // We can't pass values from the table yet + if (empty.value !== undefined) { + debug('value passing trie leaf of "%s" prevents table allocation', + node.name); + return false; + } + + const target = empty.node; + if (!targets.has(target)) { + targets.set(target, { + keys: [ child.key ], + noAdvance: child.noAdvance, + trie: empty, + }); + return true; + } + + const existing = targets.get(target)!; + + // TODO(indutny): just use it as a sub-key? + if (existing.noAdvance !== child.noAdvance) { + debug( + 'noAdvance mismatch in a trie leaf of "%s" prevents ' + + 'table allocation', + node.name); + return false; + } + + existing.keys.push(child.key); + return true; + }); + + if (bailout) { + return undefined; + } + + // We've width limit for this optimization + if (targets.size >= (1 << this.options.maxTableElemWidth)) { + debug('too many different trie targets of "%s" for a table allocation', + node.name); + return undefined; + } + + const table = new this.implementation.node.TableLookup( + new frontend.node.TableLookup(this.id.id(node.name))); + children.push(table); + + // Break the loop + if (!this.map.has(node)) { + this.map.set(node, table); + } + + targets.forEach((target) => { + const next = this.translateTrie(node, target.trie, children); + + table.ref.addEdge({ + keys: target.keys, + noAdvance: target.noAdvance, + node: next, + }); + }); + + debug('optimized "%s" to a table lookup node', node.name); + return table; + } + + private translateSequence(node: source.node.Match, trie: TrieSequence, + children: MatchChildren) + : IWrap<frontend.node.Match> { + const sequence = new this.implementation.node.Sequence( + new frontend.node.Sequence(this.id.id(node.name), trie.select)); + children.push(sequence); + + // Break the loop + if (!this.map.has(node)) { + this.map.set(node, sequence); + } + + const childNode = this.translateTrie(node, trie.child, children); + + const value = trie.child instanceof TrieEmpty ? + trie.child.value : undefined; + + sequence.ref.setEdge(childNode, value); + + return sequence; + } + + private translateCode(code: source.code.Code): WrappedCode { + const prefixed = this.codeId.id(code.name).name; + const codeImpl = this.implementation.code; + + let res: WrappedCode; + if (code instanceof source.code.IsEqual) { + res = new codeImpl.IsEqual( + new frontend.code.IsEqual(prefixed, code.field, code.value)); + } else if (code instanceof source.code.Load) { + res = new codeImpl.Load( + new frontend.code.Load(prefixed, code.field)); + } else if (code instanceof source.code.MulAdd) { + // TODO(indutny): verify property type + const m = new frontend.code.MulAdd(prefixed, code.field, { + base: code.options.base, + max: code.options.max, + signed: code.options.signed === undefined ? true : code.options.signed, + }); + res = new codeImpl.MulAdd(m); + } else if (code instanceof source.code.And) { + res = new codeImpl.And( + new frontend.code.Or(prefixed, code.field, code.value)); + } else if (code instanceof source.code.Or) { + res = new codeImpl.Or( + new frontend.code.Or(prefixed, code.field, code.value)); + } else if (code instanceof source.code.Store) { + res = new codeImpl.Store( + new frontend.code.Store(prefixed, code.field)); + } else if (code instanceof source.code.Test) { + res = new codeImpl.Test( + new frontend.code.Test(prefixed, code.field, code.value)); + } else if (code instanceof source.code.Update) { + res = new codeImpl.Update( + new frontend.code.Update(prefixed, code.field, code.value)); + + // External callbacks + } else if (code instanceof source.code.Span) { + res = new codeImpl.Span(new frontend.code.Span(code.name)); + } else if (code instanceof source.code.Match) { + res = new codeImpl.Match(new frontend.code.Match(code.name)); + } else if (code instanceof source.code.Value) { + res = new codeImpl.Value(new frontend.code.Value(code.name)); + } else { + throw new Error(`Unsupported code: "${code.name}"`); + } + + // Re-use instances to build them just once + if (this.codeCache.has(res.ref.cacheKey)) { + return this.codeCache.get(res.ref.cacheKey)!; + } + + this.codeCache.set(res.ref.cacheKey, res); + return res; + } + + private translateSpanCode(code: source.code.Span): IWrap<frontend.code.Span> { + return this.translateCode(code) as IWrap<frontend.code.Span>; + } + + private translateTransform(transform?: source.transform.Transform) + : IWrap<frontend.transform.Transform> { + const transformImpl = this.implementation.transform; + if (transform === undefined) { + return new transformImpl.ID(new frontend.transform.ID()); + } else if (transform.name === 'to_lower') { + return new transformImpl.ToLower( + new frontend.transform.ToLower()); + } else if (transform.name === 'to_lower_unsafe') { + return new transformImpl.ToLowerUnsafe( + new frontend.transform.ToLowerUnsafe()); + } else { + throw new Error(`Unsupported transform: "${transform.name}"`); + } + } +} diff --git a/llparse-frontend/src/implementation/code.ts b/llparse-frontend/src/implementation/code.ts new file mode 100644 index 0000000..c467ced --- /dev/null +++ b/llparse-frontend/src/implementation/code.ts @@ -0,0 +1,16 @@ +import * as code from '../code'; +import { IWrap } from '../wrap'; + +export interface ICodeImplementation { + readonly And: new(c: code.And) => IWrap<code.And>; + readonly IsEqual: new(c: code.IsEqual) => IWrap<code.IsEqual>; + readonly Load: new(c: code.Load) => IWrap<code.Load>; + readonly Match: new(c: code.Match) => IWrap<code.Match>; + readonly MulAdd: new(c: code.MulAdd) => IWrap<code.MulAdd>; + readonly Or: new(c: code.Or) => IWrap<code.Or>; + readonly Span: new(c: code.Span) => IWrap<code.Span>; + readonly Store: new(c: code.Store) => IWrap<code.Store>; + readonly Test: new(c: code.Test) => IWrap<code.Test>; + readonly Update: new(c: code.Update) => IWrap<code.Update>; + readonly Value: new(c: code.Value) => IWrap<code.Value>; +} diff --git a/llparse-frontend/src/implementation/full.ts b/llparse-frontend/src/implementation/full.ts new file mode 100644 index 0000000..08c4c03 --- /dev/null +++ b/llparse-frontend/src/implementation/full.ts @@ -0,0 +1,9 @@ +import { ICodeImplementation } from './code'; +import { INodeImplementation } from './node'; +import { ITransformImplementation } from './transform'; + +export interface IImplementation { + readonly code: ICodeImplementation; + readonly node: INodeImplementation; + readonly transform: ITransformImplementation; +} diff --git a/llparse-frontend/src/implementation/index.ts b/llparse-frontend/src/implementation/index.ts new file mode 100644 index 0000000..2b5411b --- /dev/null +++ b/llparse-frontend/src/implementation/index.ts @@ -0,0 +1,4 @@ +export * from './code'; +export * from './full'; +export * from './node'; +export * from './transform'; diff --git a/llparse-frontend/src/implementation/node.ts b/llparse-frontend/src/implementation/node.ts new file mode 100644 index 0000000..af0b3df --- /dev/null +++ b/llparse-frontend/src/implementation/node.ts @@ -0,0 +1,15 @@ +import * as node from '../node'; +import { IWrap } from '../wrap'; + +export interface INodeImplementation { + readonly Consume: new(n: node.Consume) => IWrap<node.Consume>; + readonly Empty: new(n: node.Empty) => IWrap<node.Empty>; + readonly Error: new(n: node.Error) => IWrap<node.Error>; + readonly Invoke: new(n: node.Invoke) => IWrap<node.Invoke>; + readonly Pause: new(n: node.Pause) => IWrap<node.Pause>; + readonly Sequence: new(n: node.Sequence) => IWrap<node.Sequence>; + readonly Single: new(n: node.Single) => IWrap<node.Single>; + readonly SpanEnd: new(n: node.SpanEnd) => IWrap<node.SpanEnd>; + readonly SpanStart: new(n: node.SpanStart) => IWrap<node.SpanStart>; + readonly TableLookup: new(n: node.TableLookup) => IWrap<node.TableLookup>; +} diff --git a/llparse-frontend/src/implementation/transform.ts b/llparse-frontend/src/implementation/transform.ts new file mode 100644 index 0000000..4382284 --- /dev/null +++ b/llparse-frontend/src/implementation/transform.ts @@ -0,0 +1,9 @@ +import * as transform from '../transform'; +import { IWrap } from '../wrap'; + +export interface ITransformImplementation { + readonly ID: new(t: transform.ID) => IWrap<transform.ID>; + readonly ToLower: new(t: transform.ToLower) => IWrap<transform.ToLower>; + readonly ToLowerUnsafe: new(t: transform.ToLowerUnsafe) + => IWrap<transform.ToLowerUnsafe>; +} diff --git a/llparse-frontend/src/namespace/frontend.ts b/llparse-frontend/src/namespace/frontend.ts new file mode 100644 index 0000000..2f89093 --- /dev/null +++ b/llparse-frontend/src/namespace/frontend.ts @@ -0,0 +1,5 @@ +import * as code from '../code'; +import * as node from '../node'; +import * as transform from '../transform'; + +export { code, node, transform }; diff --git a/llparse-frontend/src/node/base.ts b/llparse-frontend/src/node/base.ts new file mode 100644 index 0000000..1e93c49 --- /dev/null +++ b/llparse-frontend/src/node/base.ts @@ -0,0 +1,46 @@ +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Slot } from './slot'; + +export interface IReadonlyOtherwiseEdge { + readonly node: IWrap<Node>; + readonly noAdvance: boolean; + readonly value: number | undefined; +} + +interface IOtherwiseEdge { + node: IWrap<Node>; + readonly noAdvance: boolean; + readonly value: number | undefined; +} + +export abstract class Node { + private privOtherwise: IOtherwiseEdge | undefined; + private privSlots: ReadonlyArray<Slot> | undefined; + + constructor(public readonly id: IUniqueName) { + } + + public setOtherwise(node: IWrap<Node>, noAdvance: boolean, value?: number) { + this.privOtherwise = { node, noAdvance, value }; + } + + public get otherwise(): IReadonlyOtherwiseEdge | undefined { + return this.privOtherwise; + } + + public *getSlots() { + if (this.privSlots === undefined) { + this.privSlots = Array.from(this.buildSlots()); + } + + yield* this.privSlots; + } + + protected *buildSlots() { + const otherwise = this.privOtherwise; + if (otherwise !== undefined) { + yield new Slot(otherwise.node, (value) => otherwise.node = value); + } + } +} diff --git a/llparse-frontend/src/node/consume.ts b/llparse-frontend/src/node/consume.ts new file mode 100644 index 0000000..6ab49ac --- /dev/null +++ b/llparse-frontend/src/node/consume.ts @@ -0,0 +1,8 @@ +import { IUniqueName } from '../utils'; +import { Node } from './base'; + +export class Consume extends Node { + constructor(id: IUniqueName, readonly field: string) { + super(id); + } +} diff --git a/llparse-frontend/src/node/empty.ts b/llparse-frontend/src/node/empty.ts new file mode 100644 index 0000000..45c552c --- /dev/null +++ b/llparse-frontend/src/node/empty.ts @@ -0,0 +1,4 @@ +import { Node } from './base'; + +export class Empty extends Node { +} diff --git a/llparse-frontend/src/node/error.ts b/llparse-frontend/src/node/error.ts new file mode 100644 index 0000000..c4e6faf --- /dev/null +++ b/llparse-frontend/src/node/error.ts @@ -0,0 +1,9 @@ +import { IUniqueName } from '../utils'; +import { Node } from './base'; + +export class Error extends Node { + constructor(id: IUniqueName, public readonly code: number, + public readonly reason: string) { + super(id); + } +} diff --git a/llparse-frontend/src/node/index.ts b/llparse-frontend/src/node/index.ts new file mode 100644 index 0000000..bd11015 --- /dev/null +++ b/llparse-frontend/src/node/index.ts @@ -0,0 +1,13 @@ +export * from './base'; +export * from './consume'; +export * from './empty'; +export * from './error'; +export * from './invoke'; +export * from './match'; +export * from './pause'; +export * from './sequence'; +export * from './single'; +export * from './slot'; +export * from './span-end'; +export * from './span-start'; +export * from './table-lookup'; diff --git a/llparse-frontend/src/node/invoke.ts b/llparse-frontend/src/node/invoke.ts new file mode 100644 index 0000000..ba6ef53 --- /dev/null +++ b/llparse-frontend/src/node/invoke.ts @@ -0,0 +1,39 @@ +import { Code } from '../code'; +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Node } from './base'; +import { Slot } from './slot'; + +interface IInvokeEdge { + readonly code: number; + node: IWrap<Node>; +} + +export interface IReadonlyInvokeEdge { + readonly code: number; + readonly node: IWrap<Node>; +} + +export class Invoke extends Node { + private readonly privEdges: IInvokeEdge[] = []; + + constructor(id: IUniqueName, public readonly code: IWrap<Code>) { + super(id); + } + + public addEdge(code: number, node: IWrap<Node>): void { + this.privEdges.push({ code, node }); + } + + public get edges(): ReadonlyArray<IReadonlyInvokeEdge> { + return this.privEdges; + } + + protected *buildSlots() { + for (const edge of this.privEdges) { + yield new Slot(edge.node, (value) => edge.node = value); + } + + yield* super.buildSlots(); + } +} diff --git a/llparse-frontend/src/node/match.ts b/llparse-frontend/src/node/match.ts new file mode 100644 index 0000000..8a499d3 --- /dev/null +++ b/llparse-frontend/src/node/match.ts @@ -0,0 +1,11 @@ +import { Transform } from '../transform'; +import { IWrap } from '../wrap'; +import { Node } from './base'; + +export class Match extends Node { + public transform?: IWrap<Transform>; + + public setTransform(transform: IWrap<Transform>): void { + this.transform = transform; + } +} diff --git a/llparse-frontend/src/node/pause.ts b/llparse-frontend/src/node/pause.ts new file mode 100644 index 0000000..b9923d7 --- /dev/null +++ b/llparse-frontend/src/node/pause.ts @@ -0,0 +1,4 @@ +import { Error as ErrorNode } from './error'; + +export class Pause extends ErrorNode { +} diff --git a/llparse-frontend/src/node/sequence.ts b/llparse-frontend/src/node/sequence.ts new file mode 100644 index 0000000..c9105b3 --- /dev/null +++ b/llparse-frontend/src/node/sequence.ts @@ -0,0 +1,44 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Node } from './base'; +import { Match } from './match'; +import { Slot } from './slot'; + +interface ISequenceEdge { + node: IWrap<Node>; + readonly value: number | undefined; +} + +export interface IReadonlySequenceEdge { + readonly node: IWrap<Node>; + readonly value: number | undefined; +} + +export class Sequence extends Match { + private privEdge?: ISequenceEdge; + + constructor(id: IUniqueName, public readonly select: Buffer) { + super(id); + } + + public setEdge(node: IWrap<Node>, value?: number | undefined) { + assert.strictEqual(this.privEdge, undefined); + this.privEdge = { node, value }; + } + + public get edge(): IReadonlySequenceEdge | undefined { + return this.privEdge; + } + + protected *buildSlots() { + const edge = this.privEdge; + if (edge !== undefined) { + yield new Slot(edge.node, (value) => edge.node = value); + } + + yield* super.buildSlots(); + } +} diff --git a/llparse-frontend/src/node/single.ts b/llparse-frontend/src/node/single.ts new file mode 100644 index 0000000..0acf715 --- /dev/null +++ b/llparse-frontend/src/node/single.ts @@ -0,0 +1,46 @@ +import * as assert from 'assert'; + +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Node } from './base'; +import { Match } from './match'; +import { Slot } from './slot'; + +interface ISingleEdge { + readonly key: number; + node: IWrap<Node>; + readonly noAdvance: boolean; + readonly value: number | undefined; +} + +export interface IReadonlySingleEdge { + readonly key: number; + node: IWrap<Node>; + readonly noAdvance: boolean; + readonly value: number | undefined; +} + +export class Single extends Match { + private readonly privEdges: ISingleEdge[] = []; + + public addEdge(edge: IReadonlySingleEdge): void { + this.privEdges.push({ + key: edge.key, + noAdvance: edge.noAdvance, + node: edge.node, + value: edge.value, + }); + } + + public get edges(): ReadonlyArray<IReadonlySingleEdge> { + return this.privEdges; + } + + protected *buildSlots() { + for (const edge of this.privEdges) { + yield new Slot(edge.node, (value) => edge.node = value); + } + + yield* super.buildSlots(); + } +} diff --git a/llparse-frontend/src/node/slot.ts b/llparse-frontend/src/node/slot.ts new file mode 100644 index 0000000..923da86 --- /dev/null +++ b/llparse-frontend/src/node/slot.ts @@ -0,0 +1,20 @@ +import { IWrap } from '../wrap'; +import { Node } from './base'; + +export class Slot { + private privNode: IWrap<Node>; + + constructor(node: IWrap<Node>, + private readonly privUpdate: (value: IWrap<Node>) => void) { + this.privNode = node; + } + + public get node(): IWrap<Node> { + return this.privNode; + } + + public set node(value: IWrap<Node>) { + this.privNode = value; + this.privUpdate(value); + } +} diff --git a/llparse-frontend/src/node/span-end.ts b/llparse-frontend/src/node/span-end.ts new file mode 100644 index 0000000..bf8d5cc --- /dev/null +++ b/llparse-frontend/src/node/span-end.ts @@ -0,0 +1,12 @@ +import { Span } from '../code'; +import { SpanField } from '../span-field'; +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Node } from './base'; + +export class SpanEnd extends Node { + constructor(id: IUniqueName, public readonly field: SpanField, + public readonly callback: IWrap<Span>) { + super(id); + } +} diff --git a/llparse-frontend/src/node/span-start.ts b/llparse-frontend/src/node/span-start.ts new file mode 100644 index 0000000..89690f1 --- /dev/null +++ b/llparse-frontend/src/node/span-start.ts @@ -0,0 +1,12 @@ +import { Span } from '../code'; +import { SpanField } from '../span-field'; +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Node } from './base'; + +export class SpanStart extends Node { + constructor(id: IUniqueName, public readonly field: SpanField, + public readonly callback: IWrap<Span>) { + super(id); + } +} diff --git a/llparse-frontend/src/node/table-lookup.ts b/llparse-frontend/src/node/table-lookup.ts new file mode 100644 index 0000000..9880fc7 --- /dev/null +++ b/llparse-frontend/src/node/table-lookup.ts @@ -0,0 +1,43 @@ +import * as assert from 'assert'; + +import { IUniqueName } from '../utils'; +import { IWrap } from '../wrap'; +import { Node } from './base'; +import { Match } from './match'; +import { Slot } from './slot'; + +interface ITableEdge { + readonly keys: ReadonlyArray<number>; + node: IWrap<Node>; + readonly noAdvance: boolean; +} + +export interface IReadonlyTableEdge { + readonly keys: ReadonlyArray<number>; + readonly node: IWrap<Node>; + readonly noAdvance: boolean; +} + +export class TableLookup extends Match { + private readonly privEdges: ITableEdge[] = []; + + public addEdge(edge: IReadonlyTableEdge): void { + this.privEdges.push({ + keys: edge.keys, + noAdvance: edge.noAdvance, + node: edge.node, + }); + } + + public get edges(): ReadonlyArray<IReadonlyTableEdge> { + return this.privEdges; + } + + protected *buildSlots() { + for (const edge of this.privEdges) { + yield new Slot(edge.node, (value) => edge.node = value); + } + + yield* super.buildSlots(); + } +} diff --git a/llparse-frontend/src/peephole.ts b/llparse-frontend/src/peephole.ts new file mode 100644 index 0000000..19ac13f --- /dev/null +++ b/llparse-frontend/src/peephole.ts @@ -0,0 +1,52 @@ +import { Node, Empty } from './node'; +import { IWrap } from './wrap'; + +type WrapNode = IWrap<Node>; +type WrapList = ReadonlyArray<WrapNode>; + +export class Peephole { + public optimize(root: WrapNode, nodes: WrapList): WrapNode { + let changed = new Set(nodes); + + while (changed.size !== 0) { + const previous = changed; + changed = new Set(); + + for (const node of previous) { + if (this.optimizeNode(node)) { + changed.add(node); + } + } + } + + while (root.ref instanceof Empty) { + if (!root.ref.otherwise!.noAdvance) { + break; + } + + root = root.ref.otherwise!.node; + } + + return root; + } + + public optimizeNode(node: WrapNode): boolean { + let changed = false; + for (const slot of node.ref.getSlots()) { + if (!(slot.node.ref instanceof Empty)) { + continue; + } + + const otherwise = slot.node.ref.otherwise!; + + // Node actively skips, can't optimize! + if (!otherwise.noAdvance) { + continue; + } + + slot.node = otherwise.node; + changed = true; + } + return changed; + } +} diff --git a/llparse-frontend/src/span-field.ts b/llparse-frontend/src/span-field.ts new file mode 100644 index 0000000..0652f77 --- /dev/null +++ b/llparse-frontend/src/span-field.ts @@ -0,0 +1,8 @@ +import { Span } from './code'; +import { IWrap } from './wrap'; + +export class SpanField { + constructor(public readonly index: number, + public readonly callbacks: ReadonlyArray<IWrap<Span>>) { + } +} diff --git a/llparse-frontend/src/transform/base.ts b/llparse-frontend/src/transform/base.ts new file mode 100644 index 0000000..5397326 --- /dev/null +++ b/llparse-frontend/src/transform/base.ts @@ -0,0 +1,4 @@ +export abstract class Transform { + constructor(public readonly name: string) { + } +} diff --git a/llparse-frontend/src/transform/id.ts b/llparse-frontend/src/transform/id.ts new file mode 100644 index 0000000..d86e3c1 --- /dev/null +++ b/llparse-frontend/src/transform/id.ts @@ -0,0 +1,7 @@ +import { Transform } from './base'; + +export class ID extends Transform { + constructor() { + super('id'); + } +} diff --git a/llparse-frontend/src/transform/index.ts b/llparse-frontend/src/transform/index.ts new file mode 100644 index 0000000..f103b3b --- /dev/null +++ b/llparse-frontend/src/transform/index.ts @@ -0,0 +1,4 @@ +export * from './base'; +export * from './id'; +export * from './to-lower'; +export * from './to-lower-unsafe'; diff --git a/llparse-frontend/src/transform/to-lower-unsafe.ts b/llparse-frontend/src/transform/to-lower-unsafe.ts new file mode 100644 index 0000000..99d9618 --- /dev/null +++ b/llparse-frontend/src/transform/to-lower-unsafe.ts @@ -0,0 +1,7 @@ +import { Transform } from './base'; + +export class ToLowerUnsafe extends Transform { + constructor() { + super('to_lower_unsafe'); + } +} diff --git a/llparse-frontend/src/transform/to-lower.ts b/llparse-frontend/src/transform/to-lower.ts new file mode 100644 index 0000000..b333fce --- /dev/null +++ b/llparse-frontend/src/transform/to-lower.ts @@ -0,0 +1,7 @@ +import { Transform } from './base'; + +export class ToLower extends Transform { + constructor() { + super('to_lower'); + } +} diff --git a/llparse-frontend/src/trie/empty.ts b/llparse-frontend/src/trie/empty.ts new file mode 100644 index 0000000..aba52ea --- /dev/null +++ b/llparse-frontend/src/trie/empty.ts @@ -0,0 +1,9 @@ +import { node as api } from 'llparse-builder'; +import { TrieNode } from './node'; + +export class TrieEmpty extends TrieNode { + constructor(public readonly node: api.Node, + public readonly value: number | undefined) { + super(); + } +} diff --git a/llparse-frontend/src/trie/index.ts b/llparse-frontend/src/trie/index.ts new file mode 100644 index 0000000..391c6a3 --- /dev/null +++ b/llparse-frontend/src/trie/index.ts @@ -0,0 +1,136 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; +import { Edge, node as api } from 'llparse-builder'; + +import { TrieEmpty } from './empty'; +import { TrieNode } from './node'; +import { TrieSequence } from './sequence'; +import { ITrieSingleChild, TrieSingle } from './single'; + +export { TrieEmpty, TrieNode, TrieSequence, TrieSingle }; + +interface IEdge { + readonly key: Buffer; + readonly node: api.Node; + readonly noAdvance: boolean; + readonly value: number | undefined; +} + +type Path = ReadonlyArray<Buffer>; +type EdgeArray = ReadonlyArray<IEdge>; + +export class Trie { + constructor(private readonly name: string) { + } + + public build(edges: ReadonlyArray<Edge>): undefined | TrieNode { + if (edges.length === 0) { + return undefined; + } + + const internalEdges: IEdge[] = []; + for (const edge of edges) { + internalEdges.push({ + key: edge.key as Buffer, + noAdvance: edge.noAdvance, + node: edge.node, + value: edge.value, + }); + } + + return this.level(internalEdges, []); + } + + private level(edges: EdgeArray, path: Path): TrieNode { + const first = edges[0].key; + const last = edges[edges.length - 1].key; + + // Leaf + if (edges.length === 1 && edges[0].key.length === 0) { + return new TrieEmpty(edges[0].node, edges[0].value); + } + + // Find the longest common sub-string + let common = 0; + for (; common < first.length; common++) { + if (first[common] !== last[common]) { + break; + } + } + + // Sequence + if (common > 1) { + return this.sequence(edges, first.slice(0, common), path); + } + + // Single + return this.single(edges, path); + } + + private slice(edges: EdgeArray, off: number): EdgeArray { + return edges.map((edge) => { + return { + key: edge.key.slice(off), + noAdvance: edge.noAdvance, + node: edge.node, + value: edge.value, + }; + }).sort((a, b) => { + return a.key.compare(b.key); + }); + } + + private sequence(edges: EdgeArray, prefix: Buffer, path: Path): TrieNode { + const sliced = this.slice(edges, prefix.length); + const noAdvance = sliced.some((edge) => edge.noAdvance); + assert(!noAdvance); + const child = this.level(sliced, path.concat(prefix)); + + return new TrieSequence(prefix, child); + } + + private single(edges: EdgeArray, path: Path): TrieNode { + // Check for duplicates + if (edges[0].key.length === 0) { + assert(path.length !== 0, `Empty root entry at "${this.name}"`); + assert(edges.length === 1 || edges[1].key.length !== 0, + `Duplicate entries in "${this.name}" at [ ${path.join(', ')} ]`); + } + + let otherwise: TrieEmpty | undefined; + const keys: Map<number, IEdge[]> = new Map(); + for (const edge of edges) { + if (edge.key.length === 0) { + otherwise = new TrieEmpty(edge.node, edge.value); + continue; + } + const key = edge.key[0]; + + if (keys.has(key)) { + keys.get(key)!.push(edge); + } else { + keys.set(key, [ edge ]); + } + } + + const children: ITrieSingleChild[] = []; + keys.forEach((subEdges, key) => { + const sliced = this.slice(subEdges, 1); + const subpath = path.concat(Buffer.from([ key ])); + + const noAdvance = subEdges.some((edge) => edge.noAdvance); + const allSame = subEdges.every((edge) => edge.noAdvance === noAdvance); + + assert(allSame || subEdges.length === 0, + 'Conflicting `.peek()` and `.match()` entries in ' + + `"${this.name}" at [ ${subpath.join(', ')} ]`); + + children.push({ + key, + noAdvance, + node: this.level(sliced, subpath), + }); + }); + return new TrieSingle(children, otherwise); + } +} diff --git a/llparse-frontend/src/trie/node.ts b/llparse-frontend/src/trie/node.ts new file mode 100644 index 0000000..31f327c --- /dev/null +++ b/llparse-frontend/src/trie/node.ts @@ -0,0 +1,2 @@ +export abstract class TrieNode { +} diff --git a/llparse-frontend/src/trie/sequence.ts b/llparse-frontend/src/trie/sequence.ts new file mode 100644 index 0000000..6b17e02 --- /dev/null +++ b/llparse-frontend/src/trie/sequence.ts @@ -0,0 +1,9 @@ +import { node as api } from 'llparse-builder'; +import { TrieNode } from './node'; + +export class TrieSequence extends TrieNode { + constructor(public readonly select: Buffer, + public readonly child: TrieNode) { + super(); + } +} diff --git a/llparse-frontend/src/trie/single.ts b/llparse-frontend/src/trie/single.ts new file mode 100644 index 0000000..c984af0 --- /dev/null +++ b/llparse-frontend/src/trie/single.ts @@ -0,0 +1,16 @@ +import { node as api } from 'llparse-builder'; +import { TrieEmpty } from './empty'; +import { TrieNode } from './node'; + +export interface ITrieSingleChild { + readonly key: number; + readonly noAdvance: boolean; + readonly node: TrieNode; +} + +export class TrieSingle extends TrieNode { + constructor(public readonly children: ReadonlyArray<ITrieSingleChild>, + public readonly otherwise: TrieEmpty | undefined) { + super(); + } +} diff --git a/llparse-frontend/src/utils/identifier.ts b/llparse-frontend/src/utils/identifier.ts new file mode 100644 index 0000000..c9ba6ad --- /dev/null +++ b/llparse-frontend/src/utils/identifier.ts @@ -0,0 +1,32 @@ +export interface IUniqueName { + readonly name: string; + readonly originalName: string; +} + +export class Identifier { + private readonly ns: Set<string> = new Set(); + + constructor(private readonly prefix: string = '', + private readonly postfix: string = '') { + } + + public id(name: string): IUniqueName { + let target = this.prefix + name + this.postfix; + if (this.ns.has(target)) { + let i = 1; + for (; i < this.ns.size; i++) { + if (!this.ns.has(target + '_' + i)) { + break; + } + } + + target += '_' + i; + } + + this.ns.add(target); + return { + name: target, + originalName: name, + }; + } +} diff --git a/llparse-frontend/src/utils/index.ts b/llparse-frontend/src/utils/index.ts new file mode 100644 index 0000000..06e86f1 --- /dev/null +++ b/llparse-frontend/src/utils/index.ts @@ -0,0 +1,19 @@ +export { Identifier, IUniqueName } from './identifier'; + +export function toCacheKey(value: number | boolean): string { + if (typeof value === 'number') { + if (value < 0) { + return 'm' + (-value); + } else { + return value.toString(); + } + } else if (typeof value === 'boolean') { + if (value === true) { + return 'true'; + } else { + return 'false'; + } + } else { + throw new Error(`Unsupported value: "${value}"`); + } +} diff --git a/llparse-frontend/src/wrap.ts b/llparse-frontend/src/wrap.ts new file mode 100644 index 0000000..013adb3 --- /dev/null +++ b/llparse-frontend/src/wrap.ts @@ -0,0 +1,3 @@ +export interface IWrap<T> { + readonly ref: T; +} diff --git a/llparse-frontend/test/container-test.ts b/llparse-frontend/test/container-test.ts new file mode 100644 index 0000000..28b7f1b --- /dev/null +++ b/llparse-frontend/test/container-test.ts @@ -0,0 +1,46 @@ +import * as assert from 'assert'; + +import { Builder } from 'llparse-builder'; + +import { Container, ContainerWrap, Frontend, node } from '../src/frontend'; +import implementation from './fixtures/a-implementation'; +import { Node } from './fixtures/implementation/node/base'; + +describe('llparse-frontend/Container', () => { + let b: Builder; + beforeEach(() => { + b = new Builder(); + }); + + it('should translate nodes to implementation', () => { + const comb = new Container(); + comb.add('a', implementation); + comb.add('b', implementation); + + const f = new Frontend('llparse', comb.build()); + + const root = b.node('root'); + + root.match('ab', root); + root.match('acd', root); + root.match('efg', root); + root.otherwise(b.error(123, 'hello')); + + const fRoot = f.compile(root, []).root as ContainerWrap<node.Node>; + + const out: string[] = []; + (fRoot.get('a') as Node<node.Node>).build(out); + + assert.deepStrictEqual(out, [ + '<Single name=llparse__n_root k97=llparse__n_root_1 ' + + 'k101=llparse__n_root_3 otherwise-no_adv=llparse__n_error/>', + '<Single name=llparse__n_root_1 k98=llparse__n_root ' + + 'k99=llparse__n_root_2 otherwise-no_adv=llparse__n_error/>', + '<Single name=llparse__n_root_2 k100=llparse__n_root ' + + 'otherwise-no_adv=llparse__n_error/>', + '<ErrorNode name=llparse__n_error code=123 reason="hello"/>', + '<Sequence name=llparse__n_root_3 select="6667" ' + + 'otherwise-no_adv=llparse__n_error/>', + ]); + }); +}); diff --git a/llparse-frontend/test/fixtures/a-implementation/code/and.ts b/llparse-frontend/test/fixtures/a-implementation/code/and.ts new file mode 100644 index 0000000..c1df821 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/and.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class And extends Code<code.And> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/base.ts b/llparse-frontend/test/fixtures/a-implementation/code/base.ts new file mode 100644 index 0000000..d9a7ace --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/base.ts @@ -0,0 +1,6 @@ +export abstract class Code<T> { + constructor(public readonly ref: T) { + } + + public abstract build(): string; +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/index.ts b/llparse-frontend/test/fixtures/a-implementation/code/index.ts new file mode 100644 index 0000000..855a5cf --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/index.ts @@ -0,0 +1,15 @@ +import { And } from './and'; +import { IsEqual } from './is-equal'; +import { Load } from './load'; +import { Match } from './match'; +import { MulAdd } from './mul-add'; +import { Or } from './or'; +import { Span } from './span'; +import { Store } from './store'; +import { Test } from './test'; +import { Update } from './update'; +import { Value } from './value'; + +export default { + And, IsEqual, Load, Match, MulAdd, Or, Span, Store, Test, Update, Value, +}; diff --git a/llparse-frontend/test/fixtures/a-implementation/code/is-equal.ts b/llparse-frontend/test/fixtures/a-implementation/code/is-equal.ts new file mode 100644 index 0000000..13a1737 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/is-equal.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class IsEqual extends Code<code.IsEqual> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/load.ts b/llparse-frontend/test/fixtures/a-implementation/code/load.ts new file mode 100644 index 0000000..bc97f27 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/load.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Load extends Code<code.Load> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/match.ts b/llparse-frontend/test/fixtures/a-implementation/code/match.ts new file mode 100644 index 0000000..e933a71 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/match.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Match extends Code<code.Match> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/mul-add.ts b/llparse-frontend/test/fixtures/a-implementation/code/mul-add.ts new file mode 100644 index 0000000..e06a217 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/mul-add.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class MulAdd extends Code<code.MulAdd> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/or.ts b/llparse-frontend/test/fixtures/a-implementation/code/or.ts new file mode 100644 index 0000000..a569db4 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/or.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Or extends Code<code.Or> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/span.ts b/llparse-frontend/test/fixtures/a-implementation/code/span.ts new file mode 100644 index 0000000..46fc410 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/span.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Span extends Code<code.Span> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/store.ts b/llparse-frontend/test/fixtures/a-implementation/code/store.ts new file mode 100644 index 0000000..7a1ca9f --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/store.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Store extends Code<code.Store> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/test.ts b/llparse-frontend/test/fixtures/a-implementation/code/test.ts new file mode 100644 index 0000000..4fc8ddb --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/test.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Test extends Code<code.Test> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/update.ts b/llparse-frontend/test/fixtures/a-implementation/code/update.ts new file mode 100644 index 0000000..16b20e2 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/update.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Update extends Code<code.Update> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/code/value.ts b/llparse-frontend/test/fixtures/a-implementation/code/value.ts new file mode 100644 index 0000000..8e76e2a --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/code/value.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Value extends Code<code.Value> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/index.ts b/llparse-frontend/test/fixtures/a-implementation/index.ts new file mode 100644 index 0000000..1d8d29a --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/index.ts @@ -0,0 +1,5 @@ +import code from './code'; +import node from './node'; +import transform from './transform'; + +export default { code, node, transform }; diff --git a/llparse-frontend/test/fixtures/a-implementation/node/base.ts b/llparse-frontend/test/fixtures/a-implementation/node/base.ts new file mode 100644 index 0000000..04c8285 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/base.ts @@ -0,0 +1,38 @@ +import { ContainerWrap, node } from '../../../../src/frontend'; + +export abstract class Node<T extends node.Node> { + private built: boolean = false; + + constructor(public readonly ref: T) { + } + + public build(out: string[]): void { + if (this.built) { + return; + } + + this.built = true; + this.doBuild(out); + + if (this.ref.otherwise !== undefined) { + const cwrap = this.ref.otherwise.node as ContainerWrap<T>; + const otherwise = cwrap.get<Node<T>>('a'); + otherwise.build(out); + } + } + + protected format(value: string): string { + let otherwise: string = ''; + if (this.ref.otherwise !== undefined) { + const otherwiseRef = this.ref.otherwise.node.ref; + otherwise = ' otherwise' + + `${this.ref.otherwise.noAdvance ? '-no_adv' : ''}=` + + `${otherwiseRef.id.name}`; + } + + return `<${this.constructor.name} name=${this.ref.id.name} ` + + `${value}${otherwise}/>`; + } + + protected abstract doBuild(out: string[]): void; +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/consume.ts b/llparse-frontend/test/fixtures/a-implementation/node/consume.ts new file mode 100644 index 0000000..cdc6cef --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/consume.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Consume extends Node<node.Consume> { + protected doBuild(out: string[]): void { + out.push(this.format(`field=${this.ref.field}`)); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/empty.ts b/llparse-frontend/test/fixtures/a-implementation/node/empty.ts new file mode 100644 index 0000000..ef1499b --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/empty.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Empty extends Node<node.Empty> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/error.ts b/llparse-frontend/test/fixtures/a-implementation/node/error.ts new file mode 100644 index 0000000..1a4f31d --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/error.ts @@ -0,0 +1,10 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +class ErrorNode extends Node<node.Error> { + protected doBuild(out: string[]): void { + out.push(this.format(`code=${this.ref.code} reason="${this.ref.reason}"`)); + } +} + +export { ErrorNode as Error }; diff --git a/llparse-frontend/test/fixtures/a-implementation/node/index.ts b/llparse-frontend/test/fixtures/a-implementation/node/index.ts new file mode 100644 index 0000000..31dbc5e --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/index.ts @@ -0,0 +1,15 @@ +import { Consume } from './consume'; +import { Empty } from './empty'; +import { Error } from './error'; +import { Invoke } from './invoke'; +import { Pause } from './pause'; +import { Sequence } from './sequence'; +import { Single } from './single'; +import { SpanEnd } from './span-end'; +import { SpanStart } from './span-start'; +import { TableLookup } from './table-lookup'; + +export default { + Consume, Empty, Error, Invoke, Pause, Sequence, Single, SpanEnd, + SpanStart, TableLookup, +}; diff --git a/llparse-frontend/test/fixtures/a-implementation/node/invoke.ts b/llparse-frontend/test/fixtures/a-implementation/node/invoke.ts new file mode 100644 index 0000000..674be5f --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/invoke.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Invoke extends Node<node.Invoke> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/pause.ts b/llparse-frontend/test/fixtures/a-implementation/node/pause.ts new file mode 100644 index 0000000..94da63c --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/pause.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Pause extends Node<node.Pause> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/sequence.ts b/llparse-frontend/test/fixtures/a-implementation/node/sequence.ts new file mode 100644 index 0000000..13fd336 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/sequence.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Sequence extends Node<node.Sequence> { + protected doBuild(out: string[]): void { + out.push(this.format(`select="${this.ref.select.toString('hex')}"`)); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/single.ts b/llparse-frontend/test/fixtures/a-implementation/node/single.ts new file mode 100644 index 0000000..d7bcc72 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/single.ts @@ -0,0 +1,18 @@ +import { ContainerWrap, node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Single extends Node<node.Single> { + protected doBuild(out: string[]): void { + const edges: string[] = []; + for (const edge of this.ref.edges) { + edges.push(`k${edge.key}${edge.noAdvance ? '-no_adv-' : ''}=` + + `${edge.node.ref.id.name}`); + } + out.push(this.format(edges.join(' '))); + + for (const edge of this.ref.edges) { + const edgeNode = edge.node as ContainerWrap<node.Node>; + edgeNode.get<Node<node.Node>>('a').build(out); + } + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/span-end.ts b/llparse-frontend/test/fixtures/a-implementation/node/span-end.ts new file mode 100644 index 0000000..dc79b81 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/span-end.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class SpanEnd extends Node<node.SpanEnd> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/span-start.ts b/llparse-frontend/test/fixtures/a-implementation/node/span-start.ts new file mode 100644 index 0000000..32e373c --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/span-start.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class SpanStart extends Node<node.SpanStart> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/node/table-lookup.ts b/llparse-frontend/test/fixtures/a-implementation/node/table-lookup.ts new file mode 100644 index 0000000..e6166d0 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/node/table-lookup.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class TableLookup extends Node<node.TableLookup> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/transform/base.ts b/llparse-frontend/test/fixtures/a-implementation/transform/base.ts new file mode 100644 index 0000000..96dc27d --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/transform/base.ts @@ -0,0 +1,6 @@ +export abstract class Transform<T> { + constructor(public readonly ref: T) { + } + + public abstract build(): string; +} diff --git a/llparse-frontend/test/fixtures/a-implementation/transform/id.ts b/llparse-frontend/test/fixtures/a-implementation/transform/id.ts new file mode 100644 index 0000000..e6c1adc --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/transform/id.ts @@ -0,0 +1,8 @@ +import { transform } from '../../../../src/frontend'; +import { Transform } from './base'; + +export class ID extends Transform<transform.ID> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/transform/index.ts b/llparse-frontend/test/fixtures/a-implementation/transform/index.ts new file mode 100644 index 0000000..bed8bc9 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/transform/index.ts @@ -0,0 +1,5 @@ +import { ID } from './id'; +import { ToLower } from './to-lower'; +import { ToLowerUnsafe } from './to-lower-unsafe'; + +export default { ID, ToLower, ToLowerUnsafe }; diff --git a/llparse-frontend/test/fixtures/a-implementation/transform/to-lower-unsafe.ts b/llparse-frontend/test/fixtures/a-implementation/transform/to-lower-unsafe.ts new file mode 100644 index 0000000..9d175a9 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/transform/to-lower-unsafe.ts @@ -0,0 +1,8 @@ +import { transform } from '../../../../src/frontend'; +import { Transform } from './base'; + +export class ToLowerUnsafe extends Transform<transform.ToLowerUnsafe> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/a-implementation/transform/to-lower.ts b/llparse-frontend/test/fixtures/a-implementation/transform/to-lower.ts new file mode 100644 index 0000000..cbe6456 --- /dev/null +++ b/llparse-frontend/test/fixtures/a-implementation/transform/to-lower.ts @@ -0,0 +1,8 @@ +import { transform } from '../../../../src/frontend'; +import { Transform } from './base'; + +export class ToLower extends Transform<transform.ToLower> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/and.ts b/llparse-frontend/test/fixtures/implementation/code/and.ts new file mode 100644 index 0000000..c1df821 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/and.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class And extends Code<code.And> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/base.ts b/llparse-frontend/test/fixtures/implementation/code/base.ts new file mode 100644 index 0000000..d9a7ace --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/base.ts @@ -0,0 +1,6 @@ +export abstract class Code<T> { + constructor(public readonly ref: T) { + } + + public abstract build(): string; +} diff --git a/llparse-frontend/test/fixtures/implementation/code/index.ts b/llparse-frontend/test/fixtures/implementation/code/index.ts new file mode 100644 index 0000000..855a5cf --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/index.ts @@ -0,0 +1,15 @@ +import { And } from './and'; +import { IsEqual } from './is-equal'; +import { Load } from './load'; +import { Match } from './match'; +import { MulAdd } from './mul-add'; +import { Or } from './or'; +import { Span } from './span'; +import { Store } from './store'; +import { Test } from './test'; +import { Update } from './update'; +import { Value } from './value'; + +export default { + And, IsEqual, Load, Match, MulAdd, Or, Span, Store, Test, Update, Value, +}; diff --git a/llparse-frontend/test/fixtures/implementation/code/is-equal.ts b/llparse-frontend/test/fixtures/implementation/code/is-equal.ts new file mode 100644 index 0000000..13a1737 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/is-equal.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class IsEqual extends Code<code.IsEqual> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/load.ts b/llparse-frontend/test/fixtures/implementation/code/load.ts new file mode 100644 index 0000000..bc97f27 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/load.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Load extends Code<code.Load> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/match.ts b/llparse-frontend/test/fixtures/implementation/code/match.ts new file mode 100644 index 0000000..e933a71 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/match.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Match extends Code<code.Match> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/mul-add.ts b/llparse-frontend/test/fixtures/implementation/code/mul-add.ts new file mode 100644 index 0000000..e06a217 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/mul-add.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class MulAdd extends Code<code.MulAdd> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/or.ts b/llparse-frontend/test/fixtures/implementation/code/or.ts new file mode 100644 index 0000000..a569db4 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/or.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Or extends Code<code.Or> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/span.ts b/llparse-frontend/test/fixtures/implementation/code/span.ts new file mode 100644 index 0000000..46fc410 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/span.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Span extends Code<code.Span> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/store.ts b/llparse-frontend/test/fixtures/implementation/code/store.ts new file mode 100644 index 0000000..7a1ca9f --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/store.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Store extends Code<code.Store> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/test.ts b/llparse-frontend/test/fixtures/implementation/code/test.ts new file mode 100644 index 0000000..4fc8ddb --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/test.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Test extends Code<code.Test> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/update.ts b/llparse-frontend/test/fixtures/implementation/code/update.ts new file mode 100644 index 0000000..16b20e2 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/update.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Update extends Code<code.Update> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/code/value.ts b/llparse-frontend/test/fixtures/implementation/code/value.ts new file mode 100644 index 0000000..8e76e2a --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/code/value.ts @@ -0,0 +1,8 @@ +import { code } from '../../../../src/frontend'; +import { Code } from './base'; + +export class Value extends Code<code.Value> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/index.ts b/llparse-frontend/test/fixtures/implementation/index.ts new file mode 100644 index 0000000..1d8d29a --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/index.ts @@ -0,0 +1,5 @@ +import code from './code'; +import node from './node'; +import transform from './transform'; + +export default { code, node, transform }; diff --git a/llparse-frontend/test/fixtures/implementation/node/base.ts b/llparse-frontend/test/fixtures/implementation/node/base.ts new file mode 100644 index 0000000..c9fd589 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/base.ts @@ -0,0 +1,39 @@ +import { node } from '../../../../src/frontend'; + +export abstract class Node<T extends node.Node> { + private built: boolean = false; + + constructor(public readonly ref: T) { + } + + public build(out: string[]): void { + if (this.built) { + return; + } + + this.built = true; + this.doBuild(out); + + if (this.ref.otherwise !== undefined) { + (this.ref.otherwise.node as Node<T>).build(out); + } + } + + protected format(value: string): string { + let otherwise: string = ''; + if (this.ref.otherwise !== undefined) { + const otherwiseRef = this.ref.otherwise.node.ref; + otherwise = ' otherwise' + + `${this.ref.otherwise.noAdvance ? '-no_adv' : ''}=` + + `${otherwiseRef.id.name}`; + if (this.ref.otherwise.value !== undefined) { + otherwise += `:${this.ref.otherwise.value}`; + } + } + + return `<${this.constructor.name} name=${this.ref.id.name} ` + + `${value}${otherwise}/>`; + } + + protected abstract doBuild(out: string[]): void; +} diff --git a/llparse-frontend/test/fixtures/implementation/node/consume.ts b/llparse-frontend/test/fixtures/implementation/node/consume.ts new file mode 100644 index 0000000..cdc6cef --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/consume.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Consume extends Node<node.Consume> { + protected doBuild(out: string[]): void { + out.push(this.format(`field=${this.ref.field}`)); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/empty.ts b/llparse-frontend/test/fixtures/implementation/node/empty.ts new file mode 100644 index 0000000..ef1499b --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/empty.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Empty extends Node<node.Empty> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/error.ts b/llparse-frontend/test/fixtures/implementation/node/error.ts new file mode 100644 index 0000000..1a4f31d --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/error.ts @@ -0,0 +1,10 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +class ErrorNode extends Node<node.Error> { + protected doBuild(out: string[]): void { + out.push(this.format(`code=${this.ref.code} reason="${this.ref.reason}"`)); + } +} + +export { ErrorNode as Error }; diff --git a/llparse-frontend/test/fixtures/implementation/node/index.ts b/llparse-frontend/test/fixtures/implementation/node/index.ts new file mode 100644 index 0000000..31dbc5e --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/index.ts @@ -0,0 +1,15 @@ +import { Consume } from './consume'; +import { Empty } from './empty'; +import { Error } from './error'; +import { Invoke } from './invoke'; +import { Pause } from './pause'; +import { Sequence } from './sequence'; +import { Single } from './single'; +import { SpanEnd } from './span-end'; +import { SpanStart } from './span-start'; +import { TableLookup } from './table-lookup'; + +export default { + Consume, Empty, Error, Invoke, Pause, Sequence, Single, SpanEnd, + SpanStart, TableLookup, +}; diff --git a/llparse-frontend/test/fixtures/implementation/node/invoke.ts b/llparse-frontend/test/fixtures/implementation/node/invoke.ts new file mode 100644 index 0000000..674be5f --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/invoke.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Invoke extends Node<node.Invoke> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/pause.ts b/llparse-frontend/test/fixtures/implementation/node/pause.ts new file mode 100644 index 0000000..94da63c --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/pause.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Pause extends Node<node.Pause> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/sequence.ts b/llparse-frontend/test/fixtures/implementation/node/sequence.ts new file mode 100644 index 0000000..bb745f5 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/sequence.ts @@ -0,0 +1,15 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Sequence extends Node<node.Sequence> { + protected doBuild(out: string[]): void { + let str = `select="${this.ref.select.toString('hex')}" ` + + `edge="${this.ref.edge!.node.ref.id.name}"`; + if (this.ref.edge!.value !== undefined) { + str += `:${this.ref.edge!.value}`; + } + out.push(this.format(str)); + const edgeNode = this.ref.edge!.node as Node<node.Node>; + edgeNode.build(out); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/single.ts b/llparse-frontend/test/fixtures/implementation/node/single.ts new file mode 100644 index 0000000..b24ef93 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/single.ts @@ -0,0 +1,22 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Single extends Node<node.Single> { + protected doBuild(out: string[]): void { + const edges: string[] = []; + for (const edge of this.ref.edges) { + let str = `k${edge.key}${edge.noAdvance ? '-no_adv-' : ''}=` + + `${edge.node.ref.id.name}`; + if (edge.value !== undefined) { + str += `:${edge.value}`; + } + edges.push(str); + } + out.push(this.format(edges.join(' '))); + + for (const edge of this.ref.edges) { + const edgeNode = edge.node as Node<node.Node>; + edgeNode.build(out); + } + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/span-end.ts b/llparse-frontend/test/fixtures/implementation/node/span-end.ts new file mode 100644 index 0000000..dc79b81 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/span-end.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class SpanEnd extends Node<node.SpanEnd> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/span-start.ts b/llparse-frontend/test/fixtures/implementation/node/span-start.ts new file mode 100644 index 0000000..32e373c --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/span-start.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class SpanStart extends Node<node.SpanStart> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/node/table-lookup.ts b/llparse-frontend/test/fixtures/implementation/node/table-lookup.ts new file mode 100644 index 0000000..e6166d0 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/node/table-lookup.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class TableLookup extends Node<node.TableLookup> { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/llparse-frontend/test/fixtures/implementation/transform/base.ts b/llparse-frontend/test/fixtures/implementation/transform/base.ts new file mode 100644 index 0000000..96dc27d --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/transform/base.ts @@ -0,0 +1,6 @@ +export abstract class Transform<T> { + constructor(public readonly ref: T) { + } + + public abstract build(): string; +} diff --git a/llparse-frontend/test/fixtures/implementation/transform/id.ts b/llparse-frontend/test/fixtures/implementation/transform/id.ts new file mode 100644 index 0000000..e6c1adc --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/transform/id.ts @@ -0,0 +1,8 @@ +import { transform } from '../../../../src/frontend'; +import { Transform } from './base'; + +export class ID extends Transform<transform.ID> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/transform/index.ts b/llparse-frontend/test/fixtures/implementation/transform/index.ts new file mode 100644 index 0000000..bed8bc9 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/transform/index.ts @@ -0,0 +1,5 @@ +import { ID } from './id'; +import { ToLower } from './to-lower'; +import { ToLowerUnsafe } from './to-lower-unsafe'; + +export default { ID, ToLower, ToLowerUnsafe }; diff --git a/llparse-frontend/test/fixtures/implementation/transform/to-lower-unsafe.ts b/llparse-frontend/test/fixtures/implementation/transform/to-lower-unsafe.ts new file mode 100644 index 0000000..9d175a9 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/transform/to-lower-unsafe.ts @@ -0,0 +1,8 @@ +import { transform } from '../../../../src/frontend'; +import { Transform } from './base'; + +export class ToLowerUnsafe extends Transform<transform.ToLowerUnsafe> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/fixtures/implementation/transform/to-lower.ts b/llparse-frontend/test/fixtures/implementation/transform/to-lower.ts new file mode 100644 index 0000000..cbe6456 --- /dev/null +++ b/llparse-frontend/test/fixtures/implementation/transform/to-lower.ts @@ -0,0 +1,8 @@ +import { transform } from '../../../../src/frontend'; +import { Transform } from './base'; + +export class ToLower extends Transform<transform.ToLower> { + public build(): string { + return ''; + } +} diff --git a/llparse-frontend/test/frontend-test.ts b/llparse-frontend/test/frontend-test.ts new file mode 100644 index 0000000..69e075c --- /dev/null +++ b/llparse-frontend/test/frontend-test.ts @@ -0,0 +1,187 @@ +import * as assert from 'assert'; + +import * as source from 'llparse-builder'; + +import { Frontend, node } from '../src/frontend'; +import implementation from './fixtures/implementation'; +import { Node } from './fixtures/implementation/node/base'; + +function checkNodes(f: Frontend, root: source.node.Node, + expected: ReadonlyArray<string>) { + const fRoot = f.compile(root, []).root as Node<node.Node>; + + const out: string[] = []; + fRoot.build(out); + + assert.deepStrictEqual(out, expected); + + return fRoot; +} + +function checkResumptionTargets(f: Frontend, expected: ReadonlyArray<string>) { + const targets = Array.from(f.getResumptionTargets()).map((t) => { + return t.ref.id.name; + }); + + assert.deepStrictEqual(targets, expected); +} + +describe('llparse-frontend', () => { + let b: source.Builder; + let f: Frontend; + beforeEach(() => { + b = new source.Builder(); + f = new Frontend('llparse', implementation); + }); + + it('should translate nodes to implementation', () => { + const root = b.node('root'); + + root.match('ab', root); + root.match('acd', root); + root.match('efg', root); + root.otherwise(b.error(123, 'hello')); + + checkNodes(f, root, [ + '<Single name=llparse__n_root k97=llparse__n_root_1 ' + + 'k101=llparse__n_root_3 otherwise-no_adv=llparse__n_error/>', + '<Single name=llparse__n_root_1 k98=llparse__n_root ' + + 'k99=llparse__n_root_2 otherwise-no_adv=llparse__n_error/>', + '<Single name=llparse__n_root_2 k100=llparse__n_root ' + + 'otherwise-no_adv=llparse__n_error/>', + '<ErrorNode name=llparse__n_error code=123 reason="hello"/>', + '<Sequence name=llparse__n_root_3 select="6667" ' + + 'edge=\"llparse__n_root\" ' + + 'otherwise-no_adv=llparse__n_error/>', + ]); + + checkResumptionTargets(f, [ + 'llparse__n_root', + 'llparse__n_root_1', + 'llparse__n_root_3', + 'llparse__n_root_2', + ]); + }); + + it('should do peephole optimization', () => { + const root = b.node('root'); + const root1 = b.node('a'); + const root2 = b.node('b'); + const node1 = b.node('c'); + const node2 = b.node('d'); + + root.otherwise(root1); + root1.otherwise(root2); + root2.skipTo(node1); + node1.otherwise(node2); + node2.otherwise(root); + + checkNodes(f, root, [ + '<Empty name=llparse__n_b otherwise=llparse__n_b/>', + ]); + + checkResumptionTargets(f, [ + 'llparse__n_b', + ]); + }); + + it('should generate proper resumption targets', () => { + b.property('i64', 'counter'); + + const root = b.node('root'); + const end = b.node('end'); + const store = b.invoke(b.code.store('counter')); + + root.select({ a: 1, b: 2 }, store); + root.otherwise(b.error(1, 'okay')); + + store.otherwise(end); + + end.match('ohai', root); + end.match('paus', b.pause(1, 'paused').otherwise( + b.pause(2, 'paused').otherwise(root))); + end.otherwise(b.error(2, 'ohai')); + + checkNodes(f, root, [ + '<Single name=llparse__n_root k97=llparse__n_invoke_store_counter:1 ' + + 'k98=llparse__n_invoke_store_counter:2 ' + + 'otherwise-no_adv=llparse__n_error_1/>', + '<Invoke name=llparse__n_invoke_store_counter ' + + 'otherwise-no_adv=llparse__n_end/>', + '<Single name=llparse__n_end k111=llparse__n_end_1 ' + + 'k112=llparse__n_end_2 otherwise-no_adv=llparse__n_error/>', + '<Sequence name=llparse__n_end_1 select="686169" ' + + 'edge="llparse__n_root" otherwise-no_adv=llparse__n_error/>', + '<ErrorNode name=llparse__n_error code=2 reason="ohai"/>', + '<Sequence name=llparse__n_end_2 select="617573" ' + + 'edge="llparse__n_pause" otherwise-no_adv=llparse__n_error/>', + '<Pause name=llparse__n_pause otherwise-no_adv=llparse__n_pause_1/>', + '<Pause name=llparse__n_pause_1 otherwise-no_adv=llparse__n_root/>', + '<ErrorNode name=llparse__n_error_1 code=1 reason="okay"/>', + ]); + + checkResumptionTargets(f, [ + 'llparse__n_root', + 'llparse__n_end', + 'llparse__n_end_1', + 'llparse__n_end_2', + 'llparse__n_pause_1', + ]); + }); + + it('should translate Span code into Span', () => { + const root = b.invoke(b.code.span('my_span')); + root.otherwise(b.error(1, 'okay')); + + const fRoot = checkNodes(f, root, [ + '<Invoke name=llparse__n_invoke_my_span ' + + 'otherwise-no_adv=llparse__n_error/>', + '<ErrorNode name=llparse__n_error code=1 reason="okay"/>', + ]); + + assert((fRoot.ref as any).code instanceof implementation.code.Span); + }); + + it('should translate overlapping matches', () => { + const root = b.node('root'); + + root.match('ab', root); + root.match('abc', root); + root.otherwise(b.error(123, 'hello')); + + checkNodes(f, root, [ + '<Sequence name=llparse__n_root select="6162" edge="llparse__n_root_1" otherwise-no_adv=llparse__n_error/>', + '<Single name=llparse__n_root_1 k99=llparse__n_root otherwise-no_adv=llparse__n_root/>', + '<ErrorNode name=llparse__n_error code=123 reason="hello"/>', + ]); + + checkResumptionTargets(f, [ + 'llparse__n_root', + 'llparse__n_root_1', + ]); + }); + + it('should translate overlapping matches with values', () => { + const root = b.node('root'); + const store = b.invoke(b.code.store('counter')); + + root.select({ + ab: 1, + abc: 2, + }, store); + store.otherwise(root); + root.otherwise(b.error(123, 'hello')); + + checkNodes(f, root, [ + '<Sequence name=llparse__n_root select="6162" edge="llparse__n_root_1" otherwise-no_adv=llparse__n_error/>', + '<Single name=llparse__n_root_1 k99=llparse__n_invoke_store_counter:2 otherwise-no_adv=llparse__n_invoke_store_counter:1/>', + '<Invoke name=llparse__n_invoke_store_counter otherwise-no_adv=llparse__n_root/>', + '<ErrorNode name=llparse__n_error code=123 reason="hello"/>', + ]); + + checkResumptionTargets(f, [ + 'llparse__n_root', + 'llparse__n_root_1', + ]); + }); +}); diff --git a/llparse-frontend/tsconfig.json b/llparse-frontend/tsconfig.json new file mode 100644 index 0000000..01ec7c2 --- /dev/null +++ b/llparse-frontend/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2017", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./lib", + "declaration": true, + "pretty": true, + "sourceMap": true + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/llparse-frontend/tslint.json b/llparse-frontend/tslint.json new file mode 100644 index 0000000..24fec09 --- /dev/null +++ b/llparse-frontend/tslint.json @@ -0,0 +1,16 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-bitwise": null, + "max-line-length": [true, 80], + "max-classes-per-file": [true, 1, "exclude-class-expressions"], + "quotemark": [ + true, "single", "avoid-escape", "avoid-template" + ] + }, + "rulesDirectory": [] +} diff --git a/llparse/.gitignore b/llparse/.gitignore new file mode 100644 index 0000000..88b2771 --- /dev/null +++ b/llparse/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +npm-debug.log +lib/ +test/tmp/ diff --git a/llparse/.travis.yml b/llparse/.travis.yml new file mode 100644 index 0000000..b381e1b --- /dev/null +++ b/llparse/.travis.yml @@ -0,0 +1,6 @@ +sudo: false +language: node_js +node_js: + - "stable" +script: + CFLAGS="-O0" npm test diff --git a/llparse/CNAME b/llparse/CNAME new file mode 100644 index 0000000..e39566e --- /dev/null +++ b/llparse/CNAME @@ -0,0 +1 @@ +llparse.org
\ No newline at end of file diff --git a/llparse/CODE_OF_CONDUCT.md b/llparse/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4c21140 --- /dev/null +++ b/llparse/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +# Code of Conduct + +* [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md) +* [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/master/Moderation-Policy.md) diff --git a/llparse/LICENSE-MIT b/llparse/LICENSE-MIT new file mode 100644 index 0000000..6c1512d --- /dev/null +++ b/llparse/LICENSE-MIT @@ -0,0 +1,22 @@ +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/llparse/README.md b/llparse/README.md new file mode 100644 index 0000000..afbe4aa --- /dev/null +++ b/llparse/README.md @@ -0,0 +1,86 @@ +# llparse +[![Build Status](https://secure.travis-ci.org/nodejs/llparse.svg)](http://travis-ci.org/nodejs/llparse) +[![NPM version](https://badge.fury.io/js/llparse.svg)](https://badge.fury.io/js/llparse) + +An API for compiling an incremental parser into a C output. + +## Usage + +```ts +import { LLParse } from 'llparse'; + +const p = new LLParse('http_parser'); + +const method = p.node('method'); +const beforeUrl = p.node('before_url'); +const urlSpan = p.span(p.code.span('on_url')); +const url = p.node('url'); +const http = p.node('http'); + +// Add custom uint8_t property to the state +p.property('i8', 'method'); + +// Store method inside a custom property +const onMethod = p.invoke(p.code.store('method'), beforeUrl); + +// Invoke custom C function +const complete = p.invoke(p.code.match('on_complete'), { + // Restart + 0: method +}, p.error(4, '`on_complete` error')); + +method + .select({ + 'HEAD': 0, 'GET': 1, 'POST': 2, 'PUT': 3, + 'DELETE': 4, 'OPTIONS': 5, 'CONNECT': 6, + 'TRACE': 7, 'PATCH': 8 + }, onMethod) + .otherwise(p.error(5, 'Expected method')); + +beforeUrl + .match(' ', beforeUrl) + .otherwise(urlSpan.start(url)); + +url + .peek(' ', urlSpan.end(http)) + .skipTo(url); + +http + .match(' HTTP/1.1\r\n\r\n', complete) + .otherwise(p.error(6, 'Expected HTTP/1.1 and two newlines')); + +const artifacts = p.build(method); +console.log('----- C -----'); +console.log(artifacts.c); // string +console.log('----- C END -----'); +console.log('----- HEADER -----'); +console.log(artifacts.header); +console.log('----- HEADER END -----'); +``` + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2020. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +[3]: https://llvm.org/docs/LangRef.html diff --git a/llparse/_config.yml b/llparse/_config.yml new file mode 100644 index 0000000..1885487 --- /dev/null +++ b/llparse/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-midnight
\ No newline at end of file diff --git a/llparse/examples/http/.gitignore b/llparse/examples/http/.gitignore new file mode 100644 index 0000000..fcfe02e --- /dev/null +++ b/llparse/examples/http/.gitignore @@ -0,0 +1,6 @@ +http +*.c +*.ll +*.h +*.o +*.dSYM diff --git a/llparse/examples/http/Makefile b/llparse/examples/http/Makefile new file mode 100644 index 0000000..323d2e3 --- /dev/null +++ b/llparse/examples/http/Makefile @@ -0,0 +1,11 @@ +CC ?= clang + +all: http + +http: main.c http_parser.bc + $(CC) -g3 -flto -Os -fvisibility=hidden -Wall -I. http_parser.c main.c -o $@ + +http_parser.bc: index.ts + npx ts-node $< + +.PHONY = all diff --git a/llparse/examples/http/index.ts b/llparse/examples/http/index.ts new file mode 100644 index 0000000..dc7f28a --- /dev/null +++ b/llparse/examples/http/index.ts @@ -0,0 +1,51 @@ +import { LLParse } from '../../src/api'; + +const p = new LLParse('http_parser'); + +const method = p.node('method'); +const beforeUrl = p.node('before_url'); +const urlSpan = p.span(p.code.span('on_url')); +const url = p.node('url'); +const http = p.node('http'); + +// Add custom uint8_t property to the state +p.property('i8', 'method'); + +// Store method inside a custom property +const onMethod = p.invoke(p.code.store('method'), beforeUrl); + +// Invoke custom C function +const complete = p.invoke(p.code.match('on_complete'), { + // Restart + 0: method +}, p.error(4, '`on_complete` error')); + +method + .select({ + 'HEAD': 0, 'GET': 1, 'POST': 2, 'PUT': 3, + 'DELETE': 4, 'OPTIONS': 5, 'CONNECT': 6, + 'TRACE': 7, 'PATCH': 8 + }, onMethod) + .otherwise(p.error(5, 'Expected method')); + +beforeUrl + .match(' ', beforeUrl) + .otherwise(urlSpan.start(url)); + +url + .peek(' ', urlSpan.end(http)) + .skipTo(url); + +http + .match(' HTTP/1.1\r\n\r\n', complete) + .match(' HTTP/1.1\n\n', complete) + .otherwise(p.error(6, 'Expected HTTP/1.1 and two newlines')); + +// Build + +const fs = require('fs'); +const path = require('path'); + +const artifacts = p.build(method); +fs.writeFileSync(path.join(__dirname, 'http_parser.h'), artifacts.header); +fs.writeFileSync(path.join(__dirname, 'http_parser.c'), artifacts.c); diff --git a/llparse/examples/http/main.c b/llparse/examples/http/main.c new file mode 100644 index 0000000..4721a19 --- /dev/null +++ b/llparse/examples/http/main.c @@ -0,0 +1,48 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "http_parser.h" + +int on_url(http_parser_t* s, const char* p, const char* endp) { + if (p == endp) + return 0; + + fprintf(stdout, "method=%d url_part=\"%.*s\"\n", s->method, + (int) (endp - p), p); + return 0; +} + + +int on_complete(http_parser_t* s, const char* p, const char* endp) { + fprintf(stdout, "on_complete\n"); + return 0; +} + + +int main(int argc, char** argv) { + http_parser_t s; + + http_parser_init(&s); + + for (;;) { + char buf[16384]; + const char* input; + const char* endp; + int code; + + input = fgets(buf, sizeof(buf), stdin); + if (input == NULL) + break; + + endp = input + strlen(input); + code = http_parser_execute(&s, input, endp); + if (code != 0) { + fprintf(stderr, "code=%d error=%d reason=%s\n", code, s.error, s.reason); + return -1; + } + } + + return 0; +} diff --git a/llparse/package-lock.json b/llparse/package-lock.json new file mode 100644 index 0000000..e4c2b29 --- /dev/null +++ b/llparse/package-lock.json @@ -0,0 +1,1802 @@ +{ + "name": "llparse", + "version": "7.1.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/node": { + "version": "14.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", + "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "binary-search": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", + "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" + }, + "bitcode": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bitcode/-/bitcode-1.2.0.tgz", + "integrity": "sha512-cWgZK/ri/1ZUJ+UKEwP9Cqw10WY5wHz+boMxVO4vvc0btmxa2tMc2m2Zk9HYdCyx4b5+sgQM1/NCJPTIPO1XOw==", + "dev": true, + "requires": { + "bitcode-builder": "^1.2.0" + } + }, + "bitcode-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bitcode-builder/-/bitcode-builder-1.2.0.tgz", + "integrity": "sha512-biuJIhrog5d1IFMaKtHMJ8PJ1L3zxiWdclwYErjOBWf8Gwyqa4XwflvMufzcQw/OUeAArO1AqOrqsOFsWJ94OA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + } + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "llparse": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/llparse/-/llparse-6.4.0.tgz", + "integrity": "sha512-ySA+bj2wOLXrKmohAVMw0Nq84oHDPLdg+sUx4+VeSk1U72MEKfKAXS7zh82n15BRjWc/cVgWBN9RQAFdgk0g5Q==", + "dev": true, + "requires": { + "bitcode": "^1.2.0", + "debug": "^3.2.6", + "llparse-frontend": "^1.4.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "llparse-frontend": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-1.4.0.tgz", + "integrity": "sha512-lUpGvGU9MDPb3k4Wbb0S7FgpceCirXVeFQQZjsYWB3fIEGU0Q6IEiTO91J6MLLN75gsxvGiWZaKVnmcHb7jh6g==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "llparse-builder": "^1.3.2" + } + } + } + }, + "llparse-builder": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.4.0.tgz", + "integrity": "sha512-mu0/zgAc1KdD6r+tjmRvF+YgoToQvBun4iXISRfSmx66b5qurckRpYjzBUYpHn0XVqKPRrGg86gMQKv8ogY3Rw==", + "dev": true, + "requires": { + "@types/debug": "0.0.30", + "binary-search": "^1.3.6", + "debug": "^3.2.6" + }, + "dependencies": { + "@types/debug": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.30.tgz", + "integrity": "sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ==", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "llparse-frontend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-3.0.0.tgz", + "integrity": "sha512-G/o0Po2C+G5OtP8MJeQDjDf5qwDxcO7K6x4r6jqGsJwxk7yblbJnRqpmye7G/lZ8dD0Hv5neY4/KB5BhDmEc9Q==", + "requires": { + "debug": "^3.2.6", + "llparse-builder": "^1.5.2" + }, + "dependencies": { + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "llparse-builder": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz", + "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==", + "requires": { + "@types/debug": "4.1.5 ", + "binary-search": "^1.3.6", + "debug": "^4.2.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + } + } + } + } + }, + "llparse-test-fixture": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/llparse-test-fixture/-/llparse-test-fixture-5.0.1.tgz", + "integrity": "sha512-BrnS70lxODcTXttLkfoSqn8DPbNuuSLFR48JnwxLimFkr8QRNBVbUku+bumIIo5Z7gAbIGNQXDOiSi2crMzS8Q==", + "dev": true, + "requires": { + "esm": "^3.2.25", + "llparse": "^6.4.0", + "yargs": "^15.4.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.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==", + "dev": true, + "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==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", + "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.2", + "debug": "4.1.1", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "4.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + }, + "dependencies": { + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "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==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "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==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", + "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "decamelize": "^1.2.0", + "flat": "^4.1.0", + "is-plain-obj": "^1.1.0", + "yargs": "^14.2.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/llparse/package.json b/llparse/package.json new file mode 100644 index 0000000..ee35dc4 --- /dev/null +++ b/llparse/package.json @@ -0,0 +1,49 @@ +{ + "name": "llparse", + "version": "7.1.1", + "description": "Compile incremental parsers to C code", + "main": "lib/api.js", + "types": "lib/api.d.ts", + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf lib", + "prepare": "npm run clean && npm run build", + "lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", + "fix-lint": "npm run lint -- --fix", + "mocha": "mocha --timeout=10000 -r ts-node/register/type-check --reporter spec test/*-test.ts", + "test": "npm run mocha && npm run lint" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/nodejs/llparse.git" + }, + "keywords": [ + "llparse", + "compiler" + ], + "author": "Fedor Indutny <fedor@indutny.com> (http://darksi.de/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/nodejs/llparse/issues" + }, + "homepage": "https://github.com/nodejs/llparse#readme", + "devDependencies": { + "@types/debug": "^4.1.5", + "@types/mocha": "^8.0.3", + "@types/node": "^14.11.8", + "esm": "^3.2.25", + "llparse-test-fixture": "^5.0.1", + "mocha": "^8.1.3", + "ts-node": "^9.0.0", + "tslint": "^6.1.3", + "typescript": "^4.0.3" + }, + "dependencies": { + "debug": "^4.2.0", + "llparse-frontend": "^3.0.0" + } +} diff --git a/llparse/src/api.ts b/llparse/src/api.ts new file mode 100644 index 0000000..a34f5bc --- /dev/null +++ b/llparse/src/api.ts @@ -0,0 +1,47 @@ +import * as frontend from 'llparse-frontend'; + +import source = frontend.source; + +import { Compiler, ICompilerOptions, ICompilerResult } from './compiler'; + +export { source, ICompilerOptions, ICompilerResult }; + +// TODO(indutny): API for disabling/short-circuiting spans + +/** + * LLParse graph builder and compiler. + */ +export class LLParse extends source.Builder { + /** + * The prefix controls the names of methods and state struct in generated + * public C headers: + * + * ```c + * // state struct + * struct PREFIX_t { + * ... + * } + * + * int PREFIX_init(PREFIX_t* state); + * int PREFIX_execute(PREFIX_t* state, const char* p, const char* endp); + * ``` + * + * @param prefix Prefix to be used when generating public API. + */ + constructor(private readonly prefix: string = 'llparse') { + super(); + } + + /** + * Compile LLParse graph to the C code and C headers + * + * @param root Root node of the parse graph (see `.node()`) + * @param options Compiler options. + */ + public build(root: source.node.Node, options: ICompilerOptions = {}) + : ICompilerResult { + const c = new Compiler(this.prefix, options); + + return c.compile(root, this.properties); + } +} diff --git a/llparse/src/compiler/header-builder.ts b/llparse/src/compiler/header-builder.ts new file mode 100644 index 0000000..9f5bee7 --- /dev/null +++ b/llparse/src/compiler/header-builder.ts @@ -0,0 +1,80 @@ +import * as frontend from 'llparse-frontend'; +import source = frontend.source; + +export interface IHeaderBuilderOptions { + readonly prefix: string; + readonly headerGuard?: string; + readonly properties: ReadonlyArray<source.Property>; + readonly spans: ReadonlyArray<frontend.SpanField>; +} + +export class HeaderBuilder { + public build(options: IHeaderBuilderOptions): string { + let res = ''; + const PREFIX = options.prefix.toUpperCase().replace(/[^a-z]/gi, '_'); + const DEFINE = options.headerGuard === undefined ? + `INCLUDE_${PREFIX}_H_` : options.headerGuard; + + res += `#ifndef ${DEFINE}\n`; + res += `#define ${DEFINE}\n`; + res += '#ifdef __cplusplus\n'; + res += 'extern "C" {\n'; + res += '#endif\n'; + res += '\n'; + + res += '#include <stdint.h>\n'; + res += '\n'; + + // Structure + res += `typedef struct ${options.prefix}_s ${options.prefix}_t;\n`; + res += `struct ${options.prefix}_s {\n`; + res += ' int32_t _index;\n'; + + for (const [ index, field ] of options.spans.entries()) { + res += ` void* _span_pos${index};\n`; + if (field.callbacks.length > 1) { + res += ` void* _span_cb${index};\n`; + } + } + + res += ' int32_t error;\n'; + res += ' const char* reason;\n'; + res += ' const char* error_pos;\n'; + res += ' void* data;\n'; + res += ' void* _current;\n'; + + for (const prop of options.properties) { + let ty: string; + if (prop.ty === 'i8') { + ty = 'uint8_t'; + } else if (prop.ty === 'i16') { + ty = 'uint16_t'; + } else if (prop.ty === 'i32') { + ty = 'uint32_t'; + } else if (prop.ty === 'i64') { + ty = 'uint64_t'; + } else if (prop.ty === 'ptr') { + ty = 'void*'; + } else { + throw new Error( + `Unknown state property type: "${prop.ty}"`); + } + res += ` ${ty} ${prop.name};\n`; + } + res += '};\n'; + + res += '\n'; + + res += `int ${options.prefix}_init(${options.prefix}_t* s);\n`; + res += `int ${options.prefix}_execute(${options.prefix}_t* s, ` + + 'const char* p, const char* endp);\n'; + + res += '\n'; + res += '#ifdef __cplusplus\n'; + res += '} /* extern "C" *\/\n'; + res += '#endif\n'; + res += `#endif /* ${DEFINE} *\/\n`; + + return res; + } +} diff --git a/llparse/src/compiler/index.ts b/llparse/src/compiler/index.ts new file mode 100644 index 0000000..89c258a --- /dev/null +++ b/llparse/src/compiler/index.ts @@ -0,0 +1,88 @@ +import * as debugAPI from 'debug'; +import * as frontend from 'llparse-frontend'; + +import source = frontend.source; + +import * as cImpl from '../implementation/c'; +import { HeaderBuilder } from './header-builder'; + +const debug = debugAPI('llparse:compiler'); + +export interface ICompilerOptions { + /** + * Debug method name + * + * The method must have following signature: + * + * ```c + * void debug(llparse_t* state, const char* p, const char* endp, + * const char* msg); + * ``` + * + * Where `llparse_t` is a parser state type. + */ + readonly debug?: string; + + /** + * What guard define to use in `#ifndef` in C headers. + * + * Default value: `prefix` argument + */ + readonly headerGuard?: string; + + /** Optional frontend configuration */ + readonly frontend?: frontend.IFrontendLazyOptions; + + /** Optional C-backend configuration */ + readonly c?: cImpl.ICPublicOptions; +} + +export interface ICompilerResult { + /** + * Textual C code + */ + readonly c: string; + + /** + * Textual C header file + */ + readonly header: string; +} + +export class Compiler { + constructor(public readonly prefix: string, + public readonly options: ICompilerOptions) { + } + + public compile(root: source.node.Node, + properties: ReadonlyArray<source.Property>): ICompilerResult { + debug('Combining implementations'); + const container = new frontend.Container(); + + const c = new cImpl.CCompiler(container, Object.assign({ + debug: this.options.debug, + }, this.options.c)); + + debug('Running frontend pass'); + const f = new frontend.Frontend(this.prefix, + container.build(), + this.options.frontend); + const info = f.compile(root, properties); + + debug('Building header'); + const hb = new HeaderBuilder(); + + const header = hb.build({ + headerGuard: this.options.headerGuard, + prefix: this.prefix, + properties, + spans: info.spans, + }); + + debug('Building C'); + return { + header, + c: c.compile(info), + }; + } +} diff --git a/llparse/src/implementation/c/code/and.ts b/llparse/src/implementation/c/code/and.ts new file mode 100644 index 0000000..fdd5434 --- /dev/null +++ b/llparse/src/implementation/c/code/and.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class And extends Field<frontend.code.And> { + protected doBuild(ctx: Compilation, out: string[]): void { + out.push(`${this.field(ctx)} &= ${this.ref.value};`); + out.push('return 0;'); + } +} diff --git a/llparse/src/implementation/c/code/base.ts b/llparse/src/implementation/c/code/base.ts new file mode 100644 index 0000000..888330d --- /dev/null +++ b/llparse/src/implementation/c/code/base.ts @@ -0,0 +1,12 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; + +export abstract class Code<T extends frontend.code.Code> { + protected cachedDecl: string | undefined; + + constructor(public readonly ref: T) { + } + + public abstract build(ctx: Compilation, out: string[]): void; +} diff --git a/llparse/src/implementation/c/code/external.ts b/llparse/src/implementation/c/code/external.ts new file mode 100644 index 0000000..494fc5a --- /dev/null +++ b/llparse/src/implementation/c/code/external.ts @@ -0,0 +1,19 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Code } from './base'; + +export abstract class External<T extends frontend.code.External> + extends Code<T> { + + public build(ctx: Compilation, out: string[]): void { + out.push(`int ${this.ref.name}(`); + out.push(` ${ctx.prefix}_t* s, const unsigned char* p,`); + if (this.ref.signature === 'value') { + out.push(' const unsigned char* endp,'); + out.push(' int value);'); + } else { + out.push(' const unsigned char* endp);'); + } + } +} diff --git a/llparse/src/implementation/c/code/field.ts b/llparse/src/implementation/c/code/field.ts new file mode 100644 index 0000000..51f4439 --- /dev/null +++ b/llparse/src/implementation/c/code/field.ts @@ -0,0 +1,28 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Code } from './base'; + +export abstract class Field<T extends frontend.code.Field> extends Code<T> { + public build(ctx: Compilation, out: string[]): void { + out.push(`int ${this.ref.name}(`); + out.push(` ${ctx.prefix}_t* ${ctx.stateArg()},`); + out.push(` const unsigned char* ${ctx.posArg()},`); + if (this.ref.signature === 'value') { + out.push(` const unsigned char* ${ctx.endPosArg()},`); + out.push(` int ${ctx.matchVar()}) {`); + } else { + out.push(` const unsigned char* ${ctx.endPosArg()}) {`); + } + const tmp: string[] = []; + this.doBuild(ctx, tmp); + ctx.indent(out, tmp, ' '); + out.push('}'); + } + + protected abstract doBuild(ctx: Compilation, out: string[]): void; + + protected field(ctx: Compilation): string { + return `${ctx.stateArg()}->${this.ref.field}`; + } +} diff --git a/llparse/src/implementation/c/code/index.ts b/llparse/src/implementation/c/code/index.ts new file mode 100644 index 0000000..0de5de5 --- /dev/null +++ b/llparse/src/implementation/c/code/index.ts @@ -0,0 +1,27 @@ +import * as frontend from 'llparse-frontend'; + +import { And } from './and'; +import { External } from './external'; +import { IsEqual } from './is-equal'; +import { Load } from './load'; +import { MulAdd } from './mul-add'; +import { Or } from './or'; +import { Store } from './store'; +import { Test } from './test'; +import { Update } from './update'; + +export * from './base'; + +export default { + And, + IsEqual, + Load, + Match: class Match extends External<frontend.code.External> {}, + MulAdd, + Or, + Span: class Span extends External<frontend.code.Span> {}, + Store, + Test, + Update, + Value: class Value extends External<frontend.code.Value> {}, +}; diff --git a/llparse/src/implementation/c/code/is-equal.ts b/llparse/src/implementation/c/code/is-equal.ts new file mode 100644 index 0000000..f76c2c1 --- /dev/null +++ b/llparse/src/implementation/c/code/is-equal.ts @@ -0,0 +1,10 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class IsEqual extends Field<frontend.code.IsEqual> { + protected doBuild(ctx: Compilation, out: string[]): void { + out.push(`return ${this.field(ctx)} == ${this.ref.value};`); + } +} diff --git a/llparse/src/implementation/c/code/load.ts b/llparse/src/implementation/c/code/load.ts new file mode 100644 index 0000000..b913f23 --- /dev/null +++ b/llparse/src/implementation/c/code/load.ts @@ -0,0 +1,10 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class Load extends Field<frontend.code.Load> { + protected doBuild(ctx: Compilation, out: string[]): void { + out.push(`return ${this.field(ctx)};`); + } +} diff --git a/llparse/src/implementation/c/code/mul-add.ts b/llparse/src/implementation/c/code/mul-add.ts new file mode 100644 index 0000000..fd5ce8c --- /dev/null +++ b/llparse/src/implementation/c/code/mul-add.ts @@ -0,0 +1,67 @@ +import * as assert from 'assert'; +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { SIGNED_LIMITS, UNSIGNED_LIMITS, SIGNED_TYPES } from '../constants'; +import { Field } from './field'; + +export class MulAdd extends Field<frontend.code.MulAdd> { + protected doBuild(ctx: Compilation, out: string[]): void { + const options = this.ref.options; + const ty = ctx.getFieldType(this.ref.field); + + let field = this.field(ctx); + if (options.signed) { + assert(SIGNED_TYPES.has(ty), `Unexpected mulAdd type "${ty}"`); + const targetTy = SIGNED_TYPES.get(ty)!; + out.push(`${targetTy}* field = (${targetTy}*) &${field};`); + field = '(*field)'; + } + + const match = ctx.matchVar(); + + const limits = options.signed ? SIGNED_LIMITS : UNSIGNED_LIMITS; + assert(limits.has(ty), `Unexpected mulAdd type "${ty}"`); + const [ min, max ] = limits.get(ty)!; + + const mulMax = `${max} / ${options.base}`; + const mulMin = `${min} / ${options.base}`; + + out.push('/* Multiplication overflow */'); + out.push(`if (${field} > ${mulMax}) {`); + out.push(' return 1;'); + out.push('}'); + if (options.signed) { + out.push(`if (${field} < ${mulMin}) {`); + out.push(' return 1;'); + out.push('}'); + } + out.push(''); + + out.push(`${field} *= ${options.base};`); + out.push(''); + + out.push('/* Addition overflow */'); + out.push(`if (${match} >= 0) {`); + out.push(` if (${field} > ${max} - ${match}) {`); + out.push(' return 1;'); + out.push(' }'); + out.push('} else {'); + out.push(` if (${field} < ${min} - ${match}) {`); + out.push(' return 1;'); + out.push(' }'); + out.push('}'); + + out.push(`${field} += ${match};`); + + if (options.max !== undefined) { + out.push(''); + out.push('/* Enforce maximum */'); + out.push(`if (${field} > ${options.max}) {`); + out.push(' return 1;'); + out.push('}'); + } + + out.push('return 0;'); + } +} diff --git a/llparse/src/implementation/c/code/or.ts b/llparse/src/implementation/c/code/or.ts new file mode 100644 index 0000000..76b16f9 --- /dev/null +++ b/llparse/src/implementation/c/code/or.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class Or extends Field<frontend.code.Or> { + protected doBuild(ctx: Compilation, out: string[]): void { + out.push(`${this.field(ctx)} |= ${this.ref.value};`); + out.push('return 0;'); + } +} diff --git a/llparse/src/implementation/c/code/store.ts b/llparse/src/implementation/c/code/store.ts new file mode 100644 index 0000000..a37d963 --- /dev/null +++ b/llparse/src/implementation/c/code/store.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class Store extends Field<frontend.code.Store> { + protected doBuild(ctx: Compilation, out: string[]): void { + out.push(`${this.field(ctx)} = ${ctx.matchVar()};`); + out.push('return 0;'); + } +} diff --git a/llparse/src/implementation/c/code/test.ts b/llparse/src/implementation/c/code/test.ts new file mode 100644 index 0000000..36126f5 --- /dev/null +++ b/llparse/src/implementation/c/code/test.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class Test extends Field<frontend.code.Test> { + protected doBuild(ctx: Compilation, out: string[]): void { + const value = this.ref.value; + out.push(`return (${this.field(ctx)} & ${value}) == ${value};`); + } +} diff --git a/llparse/src/implementation/c/code/update.ts b/llparse/src/implementation/c/code/update.ts new file mode 100644 index 0000000..89efedf --- /dev/null +++ b/llparse/src/implementation/c/code/update.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Field } from './field'; + +export class Update extends Field<frontend.code.Update> { + protected doBuild(ctx: Compilation, out: string[]): void { + out.push(`${this.field(ctx)} = ${this.ref.value};`); + out.push('return 0;'); + } +} diff --git a/llparse/src/implementation/c/compilation.ts b/llparse/src/implementation/c/compilation.ts new file mode 100644 index 0000000..4df05a6 --- /dev/null +++ b/llparse/src/implementation/c/compilation.ts @@ -0,0 +1,336 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; +import * as frontend from 'llparse-frontend'; + +import { + CONTAINER_KEY, STATE_ERROR, + ARG_STATE, ARG_POS, ARG_ENDPOS, + VAR_MATCH, + STATE_PREFIX, LABEL_PREFIX, BLOB_PREFIX, + SEQUENCE_COMPLETE, SEQUENCE_MISMATCH, SEQUENCE_PAUSE, +} from './constants'; +import { Code } from './code'; +import { Node } from './node'; +import { Transform } from './transform'; +import { MatchSequence } from './helpers/match-sequence'; + +// Number of hex words per line of blob declaration +const BLOB_GROUP_SIZE = 11; + +type WrappedNode = frontend.IWrap<frontend.node.Node>; + +interface IBlob { + readonly alignment: number | undefined; + readonly buffer: Buffer; + readonly name: string; +} + +// TODO(indutny): deduplicate +export interface ICompilationOptions { + readonly debug?: string; +} + +// TODO(indutny): deduplicate +export interface ICompilationProperty { + readonly name: string; + readonly ty: string; +} + +export class Compilation { + private readonly stateMap: Map<string, ReadonlyArray<string>> = new Map(); + private readonly blobs: Map<Buffer, IBlob> = new Map(); + private readonly codeMap: Map<string, Code<frontend.code.Code>> = new Map(); + private readonly matchSequence: + Map<string, MatchSequence> = new Map(); + private readonly resumptionTargets: Set<string> = new Set(); + + constructor(public readonly prefix: string, + private readonly properties: ReadonlyArray<ICompilationProperty>, + resumptionTargets: ReadonlySet<WrappedNode>, + private readonly options: ICompilationOptions) { + for (const node of resumptionTargets) { + this.resumptionTargets.add(STATE_PREFIX + node.ref.id.name); + } + } + + private buildStateEnum(out: string[]): void { + out.push('enum llparse_state_e {'); + out.push(` ${STATE_ERROR},`); + for (const stateName of this.stateMap.keys()) { + if (this.resumptionTargets.has(stateName)) { + out.push(` ${stateName},`); + } + } + out.push('};'); + out.push('typedef enum llparse_state_e llparse_state_t;'); + } + + private buildBlobs(out: string[]): void { + if (this.blobs.size === 0) { + return; + } + + for (const blob of this.blobs.values()) { + const buffer = blob.buffer; + let align = ''; + if (blob.alignment) { + align = ` ALIGN(${blob.alignment})`; + } + + if (blob.alignment) { + out.push('#ifdef __SSE4_2__'); + } + out.push(`static const unsigned char${align} ${blob.name}[] = {`); + + for (let i = 0; i < buffer.length; i += BLOB_GROUP_SIZE) { + const limit = Math.min(buffer.length, i + BLOB_GROUP_SIZE); + const hex: string[] = []; + for (let j = i; j < limit; j++) { + const value = buffer[j] as number; + + const ch = String.fromCharCode(value); + // `'`, `\` + if (value === 0x27 || value === 0x5c) { + hex.push(`'\\${ch}'`); + } else if (value >= 0x20 && value <= 0x7e) { + hex.push(`'${ch}'`); + } else { + hex.push(`0x${value.toString(16)}`); + } + } + let line = ' ' + hex.join(', '); + if (limit !== buffer.length) { + line += ','; + } + out.push(line); + } + + out.push(`};`); + if (blob.alignment) { + out.push('#endif /* __SSE4_2__ */'); + } + } + out.push(''); + } + + private buildMatchSequence(out: string[]): void { + if (this.matchSequence.size === 0) { + return; + } + + MatchSequence.buildGlobals(out); + out.push(''); + + for (const match of this.matchSequence.values()) { + match.build(this, out); + out.push(''); + } + } + + public reserveSpans(spans: ReadonlyArray<frontend.SpanField>): void { + for (const span of spans) { + for (const callback of span.callbacks) { + this.buildCode(this.unwrapCode(callback)); + } + } + } + + public debug(out: string[], message: string): void { + if (this.options.debug === undefined) { + return; + } + + const args = [ + this.stateArg(), + `(const char*) ${this.posArg()}`, + `(const char*) ${this.endPosArg()}`, + ]; + + out.push(`${this.options.debug}(${args.join(', ')},`); + out.push(` ${this.cstring(message)});`); + } + + public buildGlobals(out: string[]): void { + if (this.options.debug !== undefined) { + out.push(`void ${this.options.debug}(`); + out.push(` ${this.prefix}_t* s, const char* p, const char* endp,`); + out.push(' const char* msg);'); + } + + this.buildBlobs(out); + this.buildMatchSequence(out); + this.buildStateEnum(out); + + for (const code of this.codeMap.values()) { + out.push(''); + code.build(this, out); + } + } + + public buildResumptionStates(out: string[]): void { + this.stateMap.forEach((lines, name) => { + if (!this.resumptionTargets.has(name)) { + return; + } + out.push(`case ${name}:`); + out.push(`${LABEL_PREFIX}${name}: {`); + lines.forEach((line) => out.push(` ${line}`)); + out.push(' /* UNREACHABLE */;'); + out.push(' abort();'); + out.push('}'); + }); + } + + public buildInternalStates(out: string[]): void { + this.stateMap.forEach((lines, name) => { + if (this.resumptionTargets.has(name)) { + return; + } + out.push(`${LABEL_PREFIX}${name}: {`); + lines.forEach((line) => out.push(` ${line}`)); + out.push(' /* UNREACHABLE */;'); + out.push(' abort();'); + out.push('}'); + }); + } + + public addState(state: string, lines: ReadonlyArray<string>): void { + assert(!this.stateMap.has(state)); + this.stateMap.set(state, lines); + } + + public buildCode(code: Code<frontend.code.Code>): string { + if (this.codeMap.has(code.ref.name)) { + assert.strictEqual(this.codeMap.get(code.ref.name)!, code, + `Code name conflict for "${code.ref.name}"`); + } else { + this.codeMap.set(code.ref.name, code); + } + return code.ref.name; + } + + public getFieldType(field: string): string { + for (const property of this.properties) { + if (property.name === field) { + return property.ty; + } + } + throw new Error(`Field "${field}" not found`); + } + + // Helpers + + public unwrapCode(code: frontend.IWrap<frontend.code.Code>) + : Code<frontend.code.Code> { + const container = code as frontend.ContainerWrap<frontend.code.Code>; + return container.get(CONTAINER_KEY); + } + + public unwrapNode(node: WrappedNode): Node<frontend.node.Node> { + const container = node as frontend.ContainerWrap<frontend.node.Node>; + return container.get(CONTAINER_KEY); + } + + public unwrapTransform(node: frontend.IWrap<frontend.transform.Transform>) + : Transform<frontend.transform.Transform> { + const container = + node as frontend.ContainerWrap<frontend.transform.Transform>; + return container.get(CONTAINER_KEY); + } + + public indent(out: string[], lines: ReadonlyArray<string>, pad: string) { + for (const line of lines) { + out.push(`${pad}${line}`); + } + } + + // MatchSequence cache + + public getMatchSequence( + transform: frontend.IWrap<frontend.transform.Transform>, select: Buffer) + : string { + const wrap = this.unwrapTransform(transform); + + let res: MatchSequence; + if (this.matchSequence.has(wrap.ref.name)) { + res = this.matchSequence.get(wrap.ref.name)!; + } else { + res = new MatchSequence(wrap); + this.matchSequence.set(wrap.ref.name, res); + } + + return res.getName(); + } + + // Arguments + + public stateArg(): string { + return ARG_STATE; + } + + public posArg(): string { + return ARG_POS; + } + + public endPosArg(): string { + return ARG_ENDPOS; + } + + public matchVar(): string { + return VAR_MATCH; + } + + // State fields + + public indexField(): string { + return this.stateField('_index'); + } + + public currentField(): string { + return this.stateField('_current'); + } + + public errorField(): string { + return this.stateField('error'); + } + + public reasonField(): string { + return this.stateField('reason'); + } + + public errorPosField(): string { + return this.stateField('error_pos'); + } + + public spanPosField(index: number): string { + return this.stateField(`_span_pos${index}`); + } + + public spanCbField(index: number): string { + return this.stateField(`_span_cb${index}`); + } + + public stateField(name: string): string { + return `${this.stateArg()}->${name}`; + } + + // Globals + + public cstring(value: string): string { + return JSON.stringify(value); + } + + public blob(value: Buffer, alignment?: number): string { + if (this.blobs.has(value)) { + return this.blobs.get(value)!.name; + } + + const res = BLOB_PREFIX + this.blobs.size; + this.blobs.set(value, { + alignment, + buffer: value, + name: res, + }); + return res; + } +} diff --git a/llparse/src/implementation/c/constants.ts b/llparse/src/implementation/c/constants.ts new file mode 100644 index 0000000..bfd5be3 --- /dev/null +++ b/llparse/src/implementation/c/constants.ts @@ -0,0 +1,45 @@ +export const CONTAINER_KEY = 'c'; + +export const LABEL_PREFIX = ''; +export const STATE_PREFIX = 's_n_'; +export const STATE_ERROR = 's_error'; + +export const BLOB_PREFIX = 'llparse_blob'; + +export const ARG_STATE = 'state'; +export const ARG_POS = 'p'; +export const ARG_ENDPOS = 'endp'; + +export const VAR_MATCH = 'match'; + +// MatchSequence + +export const SEQUENCE_COMPLETE = 'kMatchComplete'; +export const SEQUENCE_MISMATCH = 'kMatchMismatch'; +export const SEQUENCE_PAUSE = 'kMatchPause'; + +export const SIGNED_LIMITS: Map<string, [ string, string ]> = new Map(); +SIGNED_LIMITS.set('i8', [ '-0x80', '0x7f' ]); +SIGNED_LIMITS.set('i16', [ '-0x8000', '0x7fff' ]); +SIGNED_LIMITS.set('i32', [ '(-0x7fffffff - 1)', '0x7fffffff' ]); +SIGNED_LIMITS.set('i64', [ '(-0x7fffffffffffffffLL - 1)', + '0x7fffffffffffffffLL' ]); + +export const UNSIGNED_LIMITS: Map<string, [ string, string ]> = new Map(); +UNSIGNED_LIMITS.set('i8', [ '0', '0xff' ]); +UNSIGNED_LIMITS.set('i8', [ '0', '0xff' ]); +UNSIGNED_LIMITS.set('i16', [ '0', '0xffff' ]); +UNSIGNED_LIMITS.set('i32', [ '0', '0xffffffff' ]); +UNSIGNED_LIMITS.set('i64', [ '0ULL', '0xffffffffffffffffULL' ]); + +export const UNSIGNED_TYPES: Map<string, string> = new Map(); +UNSIGNED_TYPES.set('i8', 'uint8_t'); +UNSIGNED_TYPES.set('i16', 'uint16_t'); +UNSIGNED_TYPES.set('i32', 'uint32_t'); +UNSIGNED_TYPES.set('i64', 'uint64_t'); + +export const SIGNED_TYPES: Map<string, string> = new Map(); +SIGNED_TYPES.set('i8', 'int8_t'); +SIGNED_TYPES.set('i16', 'int16_t'); +SIGNED_TYPES.set('i32', 'int32_t'); +SIGNED_TYPES.set('i64', 'int64_t'); diff --git a/llparse/src/implementation/c/helpers/match-sequence.ts b/llparse/src/implementation/c/helpers/match-sequence.ts new file mode 100644 index 0000000..278f4b5 --- /dev/null +++ b/llparse/src/implementation/c/helpers/match-sequence.ts @@ -0,0 +1,75 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; +import * as frontend from 'llparse-frontend'; + +import { + SEQUENCE_COMPLETE, SEQUENCE_MISMATCH, SEQUENCE_PAUSE, +} from '../constants'; +import { Transform } from '../transform'; +import { Compilation } from '../compilation'; + +type TransformWrap = Transform<frontend.transform.Transform>; + +export class MatchSequence { + constructor(private readonly transform: TransformWrap) { + } + + public static buildGlobals(out: string[]): void { + out.push('enum llparse_match_status_e {'); + out.push(` ${SEQUENCE_COMPLETE},`); + out.push(` ${SEQUENCE_PAUSE},`); + out.push(` ${SEQUENCE_MISMATCH}`); + out.push('};'); + out.push('typedef enum llparse_match_status_e llparse_match_status_t;'); + out.push(''); + out.push('struct llparse_match_s {'); + out.push(' llparse_match_status_t status;'); + out.push(' const unsigned char* current;'); + out.push('};'); + out.push('typedef struct llparse_match_s llparse_match_t;'); + } + + public getName(): string { + return `llparse__match_sequence_${this.transform.ref.name}`; + } + + public build(ctx: Compilation, out: string[]): void { + out.push(`static llparse_match_t ${this.getName()}(`); + out.push(` ${ctx.prefix}_t* s, const unsigned char* p,`); + out.push(' const unsigned char* endp,'); + out.push(' const unsigned char* seq, uint32_t seq_len) {'); + + // Vars + out.push(' uint32_t index;'); + out.push(' llparse_match_t res;'); + out.push(''); + + // Body + out.push(' index = s->_index;'); + out.push(' for (; p != endp; p++) {'); + out.push(' unsigned char current;'); + out.push(''); + out.push(` current = ${this.transform.build(ctx, '*p')};`); + out.push(' if (current == seq[index]) {'); + out.push(' if (++index == seq_len) {'); + out.push(` res.status = ${SEQUENCE_COMPLETE};`); + out.push(' goto reset;'); + out.push(' }'); + out.push(' } else {'); + out.push(` res.status = ${SEQUENCE_MISMATCH};`); + out.push(' goto reset;'); + out.push(' }'); + out.push(' }'); + + out.push(' s->_index = index;'); + out.push(` res.status = ${SEQUENCE_PAUSE};`); + out.push(' res.current = p;'); + out.push(' return res;'); + + out.push('reset:'); + out.push(' s->_index = 0;'); + out.push(' res.current = p;'); + out.push(' return res;'); + out.push('}'); + } +} diff --git a/llparse/src/implementation/c/index.ts b/llparse/src/implementation/c/index.ts new file mode 100644 index 0000000..ae94d34 --- /dev/null +++ b/llparse/src/implementation/c/index.ts @@ -0,0 +1,199 @@ +import * as frontend from 'llparse-frontend'; + +import { + ARG_STATE, ARG_POS, ARG_ENDPOS, + STATE_ERROR, + VAR_MATCH, + CONTAINER_KEY, +} from './constants'; +import { Compilation } from './compilation'; +import code from './code'; +import node from './node'; +import { Node } from './node'; +import transform from './transform'; + +export interface ICCompilerOptions { + readonly debug?: string; + readonly header?: string; +} + +export interface ICPublicOptions { + readonly header?: string; +} + +export class CCompiler { + constructor(container: frontend.Container, + public readonly options: ICCompilerOptions) { + container.add(CONTAINER_KEY, { code, node, transform }); + } + + public compile(info: frontend.IFrontendResult): string { + const compilation = new Compilation(info.prefix, info.properties, + info.resumptionTargets, this.options); + const out: string[] = []; + + out.push('#include <stdlib.h>'); + out.push('#include <stdint.h>'); + out.push('#include <string.h>'); + out.push(''); + + // NOTE: Inspired by https://github.com/h2o/picohttpparser + // TODO(indutny): Windows support for SSE4.2. + // See: https://github.com/nodejs/llparse/pull/24#discussion_r299789676 + // (There is no `__SSE4_2__` define for MSVC) + out.push('#ifdef __SSE4_2__'); + out.push(' #ifdef _MSC_VER'); + out.push(' #include <nmmintrin.h>'); + out.push(' #else /* !_MSC_VER */'); + out.push(' #include <x86intrin.h>'); + out.push(' #endif /* _MSC_VER */'); + out.push('#endif /* __SSE4_2__ */'); + out.push(''); + + out.push('#ifdef _MSC_VER'); + out.push(' #define ALIGN(n) _declspec(align(n))'); + out.push('#else /* !_MSC_VER */'); + out.push(' #define ALIGN(n) __attribute__((aligned(n)))'); + out.push('#endif /* _MSC_VER */'); + + out.push(''); + out.push(`#include "${this.options.header || info.prefix}.h"`); + out.push(``); + out.push(`typedef int (*${info.prefix}__span_cb)(`); + out.push(` ${info.prefix}_t*, const char*, const char*);`); + out.push(''); + + // Queue span callbacks to be built before `executeSpans()` code gets called + // below. + compilation.reserveSpans(info.spans); + + const root = info.root as frontend.ContainerWrap<frontend.node.Node>; + const rootState = root.get<Node<frontend.node.Node>>(CONTAINER_KEY) + .build(compilation); + + compilation.buildGlobals(out); + out.push(''); + + out.push(`int ${info.prefix}_init(${info.prefix}_t* ${ARG_STATE}) {`); + out.push(` memset(${ARG_STATE}, 0, sizeof(*${ARG_STATE}));`); + out.push(` ${ARG_STATE}->_current = (void*) (intptr_t) ${rootState};`); + out.push(' return 0;'); + out.push('}'); + out.push(''); + + out.push(`static llparse_state_t ${info.prefix}__run(`); + out.push(` ${info.prefix}_t* ${ARG_STATE},`); + out.push(` const unsigned char* ${ARG_POS},`); + out.push(` const unsigned char* ${ARG_ENDPOS}) {`); + out.push(` int ${VAR_MATCH};`); + out.push(` switch ((llparse_state_t) (intptr_t) ` + + `${compilation.currentField()}) {`); + + let tmp: string[] = []; + compilation.buildResumptionStates(tmp); + compilation.indent(out, tmp, ' '); + + out.push(' default:'); + out.push(' /* UNREACHABLE */'); + out.push(' abort();'); + out.push(' }'); + + tmp = []; + compilation.buildInternalStates(tmp); + compilation.indent(out, tmp, ' '); + + out.push('}'); + out.push(''); + + + out.push(`int ${info.prefix}_execute(${info.prefix}_t* ${ARG_STATE}, ` + + `const char* ${ARG_POS}, const char* ${ARG_ENDPOS}) {`); + out.push(' llparse_state_t next;'); + out.push(''); + + out.push(' /* check lingering errors */'); + out.push(` if (${compilation.errorField()} != 0) {`); + out.push(` return ${compilation.errorField()};`); + out.push(' }'); + out.push(''); + + tmp = []; + this.restartSpans(compilation, info, tmp); + compilation.indent(out, tmp, ' '); + + const args = [ + compilation.stateArg(), + `(const unsigned char*) ${compilation.posArg()}`, + `(const unsigned char*) ${compilation.endPosArg()}`, + ]; + out.push(` next = ${info.prefix}__run(${args.join(', ')});`); + out.push(` if (next == ${STATE_ERROR}) {`); + out.push(` return ${compilation.errorField()};`); + out.push(' }'); + out.push(` ${compilation.currentField()} = (void*) (intptr_t) next;`); + out.push(''); + + tmp = []; + this.executeSpans(compilation, info, tmp); + compilation.indent(out, tmp, ' '); + + out.push(' return 0;'); + out.push('}'); + + return out.join('\n'); + } + + private restartSpans(ctx: Compilation, info: frontend.IFrontendResult, + out: string[]): void { + if (info.spans.length === 0) { + return; + } + + out.push('/* restart spans */'); + for (const span of info.spans) { + const posField = ctx.spanPosField(span.index); + + out.push(`if (${posField} != NULL) {`); + out.push(` ${posField} = (void*) ${ctx.posArg()};`); + out.push('}'); + } + out.push(''); + } + + private executeSpans(ctx: Compilation, info: frontend.IFrontendResult, + out: string[]): void { + if (info.spans.length === 0) { + return; + } + + out.push('/* execute spans */'); + for (const span of info.spans) { + const posField = ctx.spanPosField(span.index); + let callback: string; + if (span.callbacks.length === 1) { + callback = ctx.buildCode(ctx.unwrapCode(span.callbacks[0])); + } else { + callback = `(${info.prefix}__span_cb) ` + ctx.spanCbField(span.index); + callback = `(${callback})`; + } + + const args = [ + ctx.stateArg(), posField, `(const char*) ${ctx.endPosArg()}`, + ]; + + out.push(`if (${posField} != NULL) {`); + out.push(' int error;'); + out.push(''); + out.push(` error = ${callback}(${args.join(', ')});`); + + // TODO(indutny): de-duplicate this here and in SpanEnd + out.push(' if (error != 0) {'); + out.push(` ${ctx.errorField()} = error;`); + out.push(` ${ctx.errorPosField()} = ${ctx.endPosArg()};`); + out.push(' return error;'); + out.push(' }'); + out.push('}'); + } + out.push(''); + } +} diff --git a/llparse/src/implementation/c/node/base.ts b/llparse/src/implementation/c/node/base.ts new file mode 100644 index 0000000..51f90bb --- /dev/null +++ b/llparse/src/implementation/c/node/base.ts @@ -0,0 +1,77 @@ +import * as assert from 'assert'; +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { + STATE_PREFIX, LABEL_PREFIX, +} from '../constants'; + +export interface INodeEdge { + readonly node: frontend.IWrap<frontend.node.Node>; + readonly noAdvance: boolean; + readonly value?: number; +} + +export abstract class Node<T extends frontend.node.Node> { + protected cachedDecl: string | undefined; + protected privCompilation: Compilation | undefined; + + constructor(public readonly ref: T) { + } + + public build(compilation: Compilation): string { + if (this.cachedDecl !== undefined) { + return this.cachedDecl; + } + + const res = STATE_PREFIX + this.ref.id.name; + this.cachedDecl = res; + + this.privCompilation = compilation; + + const out: string[] = []; + compilation.debug(out, + `Entering node "${this.ref.id.originalName}" ("${this.ref.id.name}")`); + this.doBuild(out); + + compilation.addState(res, out); + + return res; + } + + protected get compilation(): Compilation { + assert(this.privCompilation !== undefined); + return this.privCompilation!; + } + + protected prologue(out: string[]): void { + const ctx = this.compilation; + + out.push(`if (${ctx.posArg()} == ${ctx.endPosArg()}) {`); + + const tmp: string[] = []; + this.pause(tmp); + this.compilation.indent(out, tmp, ' '); + + out.push('}'); + } + + protected pause(out: string[]): void { + out.push(`return ${this.cachedDecl};`); + } + + protected tailTo(out: string[], edge: INodeEdge): void { + const ctx = this.compilation; + const target = ctx.unwrapNode(edge.node).build(ctx); + + if (!edge.noAdvance) { + out.push(`${ctx.posArg()}++;`); + } + if (edge.value !== undefined) { + out.push(`${ctx.matchVar()} = ${edge.value};`); + } + out.push(`goto ${LABEL_PREFIX}${target};`); + } + + protected abstract doBuild(out: string[]): void; +} diff --git a/llparse/src/implementation/c/node/consume.ts b/llparse/src/implementation/c/node/consume.ts new file mode 100644 index 0000000..658a00e --- /dev/null +++ b/llparse/src/implementation/c/node/consume.ts @@ -0,0 +1,48 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Node } from './base'; + +export class Consume extends Node<frontend.node.Consume> { + public doBuild(out: string[]): void { + const ctx = this.compilation; + + const index = ctx.stateField(this.ref.field); + const ty = ctx.getFieldType(this.ref.field); + + let fieldTy: string; + if (ty === 'i64') { + fieldTy = 'uint64_t'; + } else if (ty === 'i32') { + fieldTy = 'uint32_t'; + } else if (ty === 'i16') { + fieldTy = 'uint16_t'; + } else if (ty === 'i8') { + fieldTy = 'uint8_t'; + } else { + throw new Error( + `Unsupported type ${ty} of field ${this.ref.field} for consume node`); + } + + out.push('size_t avail;'); + out.push(`${fieldTy} need;`); + + out.push(''); + out.push(`avail = ${ctx.endPosArg()} - ${ctx.posArg()};`); + out.push(`need = ${index};`); + + // Note: `avail` or `need` are going to coerced to the largest + // datatype needed to hold either of the values. + out.push('if (avail >= need) {'); + out.push(` p += need;`); + out.push(` ${index} = 0;`); + const tmp: string[] = []; + this.tailTo(tmp, this.ref.otherwise!); + ctx.indent(out, tmp, ' '); + out.push('}'); + out.push(''); + + out.push(`${index} -= avail;`); + this.pause(out); + } +} diff --git a/llparse/src/implementation/c/node/empty.ts b/llparse/src/implementation/c/node/empty.ts new file mode 100644 index 0000000..e28ecb5 --- /dev/null +++ b/llparse/src/implementation/c/node/empty.ts @@ -0,0 +1,16 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Node } from './base'; + +export class Empty extends Node<frontend.node.Empty> { + public doBuild(out: string[]): void { + const otherwise = this.ref.otherwise!; + + if (!otherwise.noAdvance) { + this.prologue(out); + } + + this.tailTo(out, otherwise); + } +} diff --git a/llparse/src/implementation/c/node/error.ts b/llparse/src/implementation/c/node/error.ts new file mode 100644 index 0000000..29dce63 --- /dev/null +++ b/llparse/src/implementation/c/node/error.ts @@ -0,0 +1,33 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { STATE_ERROR } from '../constants'; +import { Node } from './base'; + +class ErrorNode<T extends frontend.node.Error> extends Node<T> { + protected storeError(out: string[]): void { + const ctx = this.compilation; + + let hexCode: string; + if (this.ref.code < 0) { + hexCode = `-0x` + this.ref.code.toString(16); + } else { + hexCode = '0x' + this.ref.code.toString(16); + } + + out.push(`${ctx.errorField()} = ${hexCode};`); + out.push(`${ctx.reasonField()} = ${ctx.cstring(this.ref.reason)};`); + out.push(`${ctx.errorPosField()} = (const char*) ${ctx.posArg()};`); + } + + public doBuild(out: string[]): void { + this.storeError(out); + + // Non-recoverable state + out.push(`${this.compilation.currentField()} = ` + + `(void*) (intptr_t) ${STATE_ERROR};`); + out.push(`return ${STATE_ERROR};`); + } +} + +export { ErrorNode as Error }; diff --git a/llparse/src/implementation/c/node/index.ts b/llparse/src/implementation/c/node/index.ts new file mode 100644 index 0000000..ba751d9 --- /dev/null +++ b/llparse/src/implementation/c/node/index.ts @@ -0,0 +1,27 @@ +import * as frontend from 'llparse-frontend'; + +import { Consume } from './consume'; +import { Empty } from './empty'; +import { Error as ErrorNode } from './error'; +import { Invoke } from './invoke'; +import { Pause } from './pause'; +import { Sequence } from './sequence'; +import { Single } from './single'; +import { SpanEnd } from './span-end'; +import { SpanStart } from './span-start'; +import { TableLookup } from './table-lookup'; + +export { Node } from './base'; + +export default { + Consume, + Empty, + Error: class Error extends ErrorNode<frontend.node.Error> {}, + Invoke, + Pause, + Sequence, + Single, + SpanEnd, + SpanStart, + TableLookup, +}; diff --git a/llparse/src/implementation/c/node/invoke.ts b/llparse/src/implementation/c/node/invoke.ts new file mode 100644 index 0000000..ee917e9 --- /dev/null +++ b/llparse/src/implementation/c/node/invoke.ts @@ -0,0 +1,44 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Node } from './base'; + +export class Invoke extends Node<frontend.node.Invoke> { + public doBuild(out: string[]): void { + const ctx = this.compilation; + + const code = ctx.unwrapCode(this.ref.code); + const codeDecl = ctx.buildCode(code); + + const args: string[] = [ + ctx.stateArg(), + ctx.posArg(), + ctx.endPosArg(), + ]; + + const signature = code.ref.signature; + if (signature === 'value') { + args.push(ctx.matchVar()); + } + + out.push(`switch (${codeDecl}(${args.join(', ')})) {`); + let tmp: string[]; + + for (const edge of this.ref.edges) { + out.push(` case ${edge.code}:`); + tmp = []; + this.tailTo(tmp, { + noAdvance: true, + node: edge.node, + value: undefined, + }); + ctx.indent(out, tmp, ' '); + } + + out.push(' default:'); + tmp = []; + this.tailTo(tmp, this.ref.otherwise!); + ctx.indent(out, tmp, ' '); + out.push('}'); + } +} diff --git a/llparse/src/implementation/c/node/pause.ts b/llparse/src/implementation/c/node/pause.ts new file mode 100644 index 0000000..c239b46 --- /dev/null +++ b/llparse/src/implementation/c/node/pause.ts @@ -0,0 +1,19 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { STATE_ERROR } from '../constants'; +import { Error as ErrorNode } from './error'; + +export class Pause extends ErrorNode<frontend.node.Pause> { + public doBuild(out: string[]): void { + const ctx = this.compilation; + + this.storeError(out); + + // Recoverable state + const otherwise = ctx.unwrapNode(this.ref.otherwise!.node).build(ctx); + out.push(`${ctx.currentField()} = ` + + `(void*) (intptr_t) ${otherwise};`); + out.push(`return ${STATE_ERROR};`); + } +} diff --git a/llparse/src/implementation/c/node/sequence.ts b/llparse/src/implementation/c/node/sequence.ts new file mode 100644 index 0000000..73d8816 --- /dev/null +++ b/llparse/src/implementation/c/node/sequence.ts @@ -0,0 +1,55 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { + SEQUENCE_COMPLETE, SEQUENCE_MISMATCH, SEQUENCE_PAUSE, +} from '../constants'; +import { Node } from './base'; + +export class Sequence extends Node<frontend.node.Sequence> { + public doBuild(out: string[]): void { + const ctx = this.compilation; + + out.push('llparse_match_t match_seq;'); + out.push(''); + + this.prologue(out); + + const matchSequence = ctx.getMatchSequence(this.ref.transform!, + this.ref.select); + + out.push(`match_seq = ${matchSequence}(${ctx.stateArg()}, ` + + `${ctx.posArg()}, ` + + `${ctx.endPosArg()}, ${ctx.blob(this.ref.select)}, ` + + `${this.ref.select.length});`); + out.push('p = match_seq.current;'); + + let tmp: string[]; + + out.push('switch (match_seq.status) {'); + + out.push(` case ${SEQUENCE_COMPLETE}: {`); + tmp = []; + this.tailTo(tmp, { + noAdvance: false, + node: this.ref.edge!.node, + value: this.ref.edge!.value, + }); + ctx.indent(out, tmp, ' '); + out.push(' }'); + + out.push(` case ${SEQUENCE_PAUSE}: {`); + tmp = []; + this.pause(tmp); + ctx.indent(out, tmp, ' '); + out.push(' }'); + + out.push(` case ${SEQUENCE_MISMATCH}: {`); + tmp = []; + this.tailTo(tmp, this.ref.otherwise!); + ctx.indent(out, tmp, ' '); + out.push(' }'); + + out.push('}'); + } +} diff --git a/llparse/src/implementation/c/node/single.ts b/llparse/src/implementation/c/node/single.ts new file mode 100644 index 0000000..b9c8811 --- /dev/null +++ b/llparse/src/implementation/c/node/single.ts @@ -0,0 +1,47 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Node } from './base'; + +export class Single extends Node<frontend.node.Single> { + public doBuild(out: string[]): void { + const ctx = this.compilation; + + const otherwise = this.ref.otherwise!; + + this.prologue(out); + + const transform = ctx.unwrapTransform(this.ref.transform!); + const current = transform.build(ctx, `*${ctx.posArg()}`); + + out.push(`switch (${current}) {`) + this.ref.edges.forEach((edge) => { + let ch: string; + + // Non-printable ASCII, or single-quote, or forward slash + if (edge.key < 0x20 || edge.key > 0x7e || edge.key === 0x27 || + edge.key === 0x5c) { + ch = edge.key.toString(); + } else { + ch = `'${String.fromCharCode(edge.key)}'`; + } + out.push(` case ${ch}: {`); + + const tmp: string[] = []; + this.tailTo(tmp, edge); + ctx.indent(out, tmp, ' '); + + out.push(' }'); + }); + + out.push(` default: {`); + + const tmp: string[] = []; + this.tailTo(tmp, otherwise); + ctx.indent(out, tmp, ' '); + + out.push(' }'); + + out.push(`}`); + } +} diff --git a/llparse/src/implementation/c/node/span-end.ts b/llparse/src/implementation/c/node/span-end.ts new file mode 100644 index 0000000..09f97e5 --- /dev/null +++ b/llparse/src/implementation/c/node/span-end.ts @@ -0,0 +1,56 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { STATE_ERROR } from '../constants'; +import { Node } from './base'; + +export class SpanEnd extends Node<frontend.node.SpanEnd> { + public doBuild(out: string[]): void { + out.push('const unsigned char* start;'); + out.push('int err;'); + out.push(''); + + const ctx = this.compilation; + const field = this.ref.field; + const posField = ctx.spanPosField(field.index); + + // Load start position + out.push(`start = ${posField};`); + + // ...and reset + out.push(`${posField} = NULL;`); + + // Invoke callback + const callback = ctx.buildCode(ctx.unwrapCode(this.ref.callback)); + out.push(`err = ${callback}(${ctx.stateArg()}, start, ${ctx.posArg()});`); + + out.push('if (err != 0) {'); + const tmp: string[] = []; + this.buildError(tmp, 'err'); + ctx.indent(out, tmp, ' '); + out.push('}'); + + const otherwise = this.ref.otherwise!; + this.tailTo(out, otherwise); + } + + private buildError(out: string[], code: string) { + const ctx = this.compilation; + + out.push(`${ctx.errorField()} = ${code};`); + + const otherwise = this.ref.otherwise!; + let resumePos = ctx.posArg(); + if (!otherwise.noAdvance) { + resumePos = `(${resumePos} + 1)`; + } + + out.push(`${ctx.errorPosField()} = (const char*) ${resumePos};`); + + const resumptionTarget = ctx.unwrapNode(otherwise.node).build(ctx); + out.push(`${ctx.currentField()} = ` + + `(void*) (intptr_t) ${resumptionTarget};`); + + out.push(`return ${STATE_ERROR};`); + } +} diff --git a/llparse/src/implementation/c/node/span-start.ts b/llparse/src/implementation/c/node/span-start.ts new file mode 100644 index 0000000..445da67 --- /dev/null +++ b/llparse/src/implementation/c/node/span-start.ts @@ -0,0 +1,26 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Node } from './base'; + +export class SpanStart extends Node<frontend.node.SpanStart> { + public doBuild(out: string[]): void { + // Prevent spurious empty spans + this.prologue(out); + + const ctx = this.compilation; + const field = this.ref.field; + + const posField = ctx.spanPosField(field.index); + out.push(`${posField} = (void*) ${ctx.posArg()};`); + + if (field.callbacks.length > 1) { + const cbField = ctx.spanCbField(field.index); + const callback = ctx.unwrapCode(this.ref.callback); + out.push(`${cbField} = ${ctx.buildCode(callback)};`); + } + + const otherwise = this.ref.otherwise!; + this.tailTo(out, otherwise); + } +} diff --git a/llparse/src/implementation/c/node/table-lookup.ts b/llparse/src/implementation/c/node/table-lookup.ts new file mode 100644 index 0000000..6a400a3 --- /dev/null +++ b/llparse/src/implementation/c/node/table-lookup.ts @@ -0,0 +1,196 @@ +import * as assert from 'assert'; +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Node } from './base'; + +const MAX_CHAR = 0xff; +const TABLE_GROUP = 16; + +// _mm_cmpestri takes 8 ranges +const SSE_RANGES_LEN = 16; +// _mm_cmpestri takes 128bit input +const SSE_RANGES_PAD = 16; +const MAX_SSE_CALLS = 2; +const SSE_ALIGNMENT = 16; + +interface ITable { + readonly name: string; + readonly declaration: ReadonlyArray<string>; +} + +export class TableLookup extends Node<frontend.node.TableLookup> { + public doBuild(out: string[]): void { + const ctx = this.compilation; + + const table = this.buildTable(); + for (const line of table.declaration) { + out.push(line); + } + + this.prologue(out); + + const transform = ctx.unwrapTransform(this.ref.transform!); + + // Try to vectorize nodes matching characters and looping to themselves + // NOTE: `switch` below triggers when there is not enough characters in the + // stream for vectorized processing. + this.buildSSE(out); + + const current = transform.build(ctx, `*${ctx.posArg()}`); + out.push(`switch (${table.name}[(uint8_t) ${current}]) {`); + + for (const [ index, edge ] of this.ref.edges.entries()) { + out.push(` case ${index + 1}: {`); + + const tmp: string[] = []; + const edge = this.ref.edges[index]; + this.tailTo(tmp, { + noAdvance: edge.noAdvance, + node: edge.node, + value: undefined, + }); + ctx.indent(out, tmp, ' '); + + out.push(' }'); + } + + out.push(` default: {`); + + const tmp: string[] = []; + this.tailTo(tmp, this.ref.otherwise!); + ctx.indent(out, tmp, ' '); + + out.push(' }'); + out.push('}'); + } + + private buildSSE(out: string[]): boolean { + const ctx = this.compilation; + + // Transformation is not supported atm + if (this.ref.transform && this.ref.transform.ref.name !== 'id') { + return false; + } + + if (this.ref.edges.length !== 1) { + return false; + } + + const edge = this.ref.edges[0]; + if (edge.node.ref !== this.ref) { + return false; + } + + // NOTE: keys are sorted + let ranges: number[] = []; + let first: number | undefined; + let last: number | undefined; + for (const key of edge.keys) { + if (first === undefined) { + first = key; + } + if (last === undefined) { + last = key; + } + + if (key - last > 1) { + ranges.push(first, last); + first = key; + } + last = key; + } + if (first !== undefined && last !== undefined) { + ranges.push(first, last); + } + + if (ranges.length === 0) { + return false; + } + + // Way too many calls would be required + if (ranges.length > MAX_SSE_CALLS * SSE_RANGES_LEN) { + return false; + } + + out.push('#ifdef __SSE4_2__'); + out.push(`if (${ctx.endPosArg()} - ${ctx.posArg()} >= 16) {`); + out.push(' __m128i ranges;'); + out.push(' __m128i input;'); + out.push(' int avail;'); + out.push(' int match_len;'); + out.push(''); + out.push(' /* Load input */'); + out.push(` input = _mm_loadu_si128((__m128i const*) ${ctx.posArg()});`); + for (let off = 0; off < ranges.length; off += SSE_RANGES_LEN) { + const subRanges = ranges.slice(off, off + SSE_RANGES_LEN); + + let paddedRanges = subRanges.slice(); + while (paddedRanges.length < SSE_RANGES_PAD) { + paddedRanges.push(0); + } + + const blob = ctx.blob(Buffer.from(paddedRanges), SSE_ALIGNMENT); + out.push(` ranges = _mm_loadu_si128((__m128i const*) ${blob});`); + out.push(''); + + out.push(' /* Find first character that does not match `ranges` */'); + out.push(` match_len = _mm_cmpestri(ranges, ${subRanges.length},`); + out.push(' input, 16,'); + out.push(' _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES |'); + out.push(' _SIDD_NEGATIVE_POLARITY);'); + out.push(''); + out.push(' if (match_len != 0) {'); + out.push(` ${ctx.posArg()} += match_len;`); + + const tmp: string[] = []; + assert.strictEqual(edge.noAdvance, false); + this.tailTo(tmp, { + noAdvance: true, + node: edge.node, + }); + ctx.indent(out, tmp, ' '); + + out.push(' }'); + } + + { + const tmp: string[] = []; + this.tailTo(tmp, this.ref.otherwise!); + ctx.indent(out, tmp, ' '); + } + out.push('}'); + + out.push('#endif /* __SSE4_2__ */'); + + return true; + } + + private buildTable(): ITable { + const table: number[] = new Array(MAX_CHAR + 1).fill(0); + + for (const [ index, edge ] of this.ref.edges.entries()) { + edge.keys.forEach((key) => { + assert.strictEqual(table[key], 0); + table[key] = index + 1; + }); + } + + const lines = [ + 'static uint8_t lookup_table[] = {', + ]; + for (let i = 0; i < table.length; i += TABLE_GROUP) { + let line = ` ${table.slice(i, i + TABLE_GROUP).join(', ')}`; + if (i + TABLE_GROUP < table.length) { + line += ','; + } + lines.push(line); + } + lines.push('};'); + + return { + name: 'lookup_table', + declaration: lines, + }; + } +} diff --git a/llparse/src/implementation/c/transform/base.ts b/llparse/src/implementation/c/transform/base.ts new file mode 100644 index 0000000..82028d5 --- /dev/null +++ b/llparse/src/implementation/c/transform/base.ts @@ -0,0 +1,10 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; + +export abstract class Transform<T extends frontend.transform.Transform> { + constructor(public readonly ref: T) { + } + + public abstract build(ctx: Compilation, value: string): string; +} diff --git a/llparse/src/implementation/c/transform/id.ts b/llparse/src/implementation/c/transform/id.ts new file mode 100644 index 0000000..6c6105f --- /dev/null +++ b/llparse/src/implementation/c/transform/id.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Transform } from './base'; + +export class ID extends Transform<frontend.transform.ID> { + public build(ctx: Compilation, value: string): string { + // Identity transformation + return value; + } +} diff --git a/llparse/src/implementation/c/transform/index.ts b/llparse/src/implementation/c/transform/index.ts new file mode 100644 index 0000000..c13ba50 --- /dev/null +++ b/llparse/src/implementation/c/transform/index.ts @@ -0,0 +1,11 @@ +import { ID } from './id'; +import { ToLower } from './to-lower'; +import { ToLowerUnsafe } from './to-lower-unsafe'; + +export { Transform } from './base'; + +export default { + ID, + ToLower, + ToLowerUnsafe, +}; diff --git a/llparse/src/implementation/c/transform/to-lower-unsafe.ts b/llparse/src/implementation/c/transform/to-lower-unsafe.ts new file mode 100644 index 0000000..27f608c --- /dev/null +++ b/llparse/src/implementation/c/transform/to-lower-unsafe.ts @@ -0,0 +1,10 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Transform } from './base'; + +export class ToLowerUnsafe extends Transform<frontend.transform.ToLowerUnsafe> { + public build(ctx: Compilation, value: string): string { + return `((${value}) | 0x20)`; + } +} diff --git a/llparse/src/implementation/c/transform/to-lower.ts b/llparse/src/implementation/c/transform/to-lower.ts new file mode 100644 index 0000000..f639ef1 --- /dev/null +++ b/llparse/src/implementation/c/transform/to-lower.ts @@ -0,0 +1,11 @@ +import * as frontend from 'llparse-frontend'; + +import { Compilation } from '../compilation'; +import { Transform } from './base'; + +export class ToLower extends Transform<frontend.transform.ToLower> { + public build(ctx: Compilation, value: string): string { + return `((${value}) >= 'A' && (${value}) <= 'Z' ? ` + + `(${value} | 0x20) : (${value}))`; + } +} diff --git a/llparse/test/code-test.ts b/llparse/test/code-test.ts new file mode 100644 index 0000000..54c3f85 --- /dev/null +++ b/llparse/test/code-test.ts @@ -0,0 +1,168 @@ +import * as assert from 'assert'; + +import { LLParse } from '../src/api'; + +import { build, NUM_SELECT, printMatch, printOff } from './fixtures'; + +describe('llparse/code', () => { + let p: LLParse; + + beforeEach(() => { + p = new LLParse(); + }); + + describe('`.mulAdd()`', () => { + it('should operate normally', async () => { + const start = p.node('start'); + const dot = p.node('dot'); + + p.property('i64', 'counter'); + + const is1337 = p.invoke(p.code.load('counter'), { + 1337: printOff(p, p.invoke(p.code.update('counter', 0), start)), + }, p.error(1, 'Invalid result')); + + const count = p.invoke(p.code.mulAdd('counter', { base: 10 }), start); + + start + .select(NUM_SELECT, count) + .otherwise(dot); + + dot + .match('.', is1337) + .otherwise(p.error(1, 'Unexpected')); + + const binary = await build(p, start, 'mul-add'); + await binary.check('1337.', 'off=5\n'); + }); + + it('should operate fail on overflow', async () => { + const start = p.node('start'); + + p.property('i8', 'counter'); + + const count = p.invoke(p.code.mulAdd('counter', { base: 10 }), { + 1: printOff(p, start), + }, start); + + start + .select(NUM_SELECT, count) + .otherwise(p.error(1, 'Unexpected')); + + const binary = await build(p, start, 'mul-add-overflow'); + await binary.check('1111', 'off=4\n'); + }); + + it('should operate fail on greater than max', async () => { + const start = p.node('start'); + + p.property('i64', 'counter'); + + const count = p.invoke(p.code.mulAdd('counter', { + base: 10, + max: 1000, + }), { + 1: printOff(p, start), + }, start); + + start + .select(NUM_SELECT, count) + .otherwise(p.error(1, 'Unexpected')); + + const binary = await build(p, start, 'mul-add-max-overflow'); + await binary.check('1111', 'off=4\n'); + }); + }); + + describe('`.update()`', () => { + it('should operate normally', async () => { + const start = p.node('start'); + + p.property('i64', 'counter'); + + const update = p.invoke(p.code.update('counter', 42)); + + start + .skipTo(update); + + update + .otherwise(p.invoke(p.code.load('counter'), { + 42: printOff(p, start), + }, p.error(1, 'Unexpected'))); + + const binary = await build(p, start, 'update'); + await binary.check('.', 'off=1\n'); + }); + }); + + describe('`.isEqual()`', () => { + it('should operate normally', async () => { + const start = p.node('start'); + + p.property('i64', 'counter'); + + const check = p.invoke(p.code.isEqual('counter', 1), { + 0: printOff(p, start), + 1: start, + }, p.error(1, 'Unexpected')); + + start + .select(NUM_SELECT, p.invoke(p.code.store('counter'), check)) + .otherwise(p.error(1, 'Unexpected')); + + const binary = await build(p, start, 'is-equal'); + await binary.check('010', 'off=1\noff=3\n'); + }); + }); + + describe('`.or()`/`.and()`/`.test()`', () => { + it('should set and retrieve bits', async () => { + const start = p.node('start'); + const test = p.node('test'); + + p.property('i64', 'flag'); + + start + .match('1', p.invoke(p.code.or('flag', 1), start)) + .match('2', p.invoke(p.code.or('flag', 2), start)) + .match('4', p.invoke(p.code.or('flag', 4), start)) + // Reset + .match('r', p.invoke(p.code.update('flag', 0), start)) + // Partial Reset + .match('p', p.invoke(p.code.and('flag', ~1), start)) + // Test + .match('-', test) + .otherwise(p.error(1, 'start')); + + test + .match('1', p.invoke(p.code.test('flag', 1), { + 0: test, + 1: printOff(p, test), + }, p.error(2, 'test-1'))) + .match('2', p.invoke(p.code.test('flag', 2), { + 0: test, + 1: printOff(p, test), + }, p.error(3, 'test-2'))) + .match('4', p.invoke(p.code.test('flag', 4), { + 0: test, + 1: printOff(p, test), + }, p.error(4, 'test-3'))) + .match('7', p.invoke(p.code.test('flag', 7), { + 0: test, + 1: printOff(p, test), + }, p.error(5, 'test-7'))) + // Restart + .match('.', start) + .otherwise(p.error(6, 'test')); + + const binary = await build(p, start, 'or-test'); + await binary.check('1-124.2-1247.4-1247.r4-124.r12p-12', [ + 'off=3', + 'off=9', 'off=10', + 'off=16', 'off=17', 'off=18', 'off=19', + 'off=26', + 'off=34', + ]); + }); + }); +}); diff --git a/llparse/test/compiler-test.ts b/llparse/test/compiler-test.ts new file mode 100644 index 0000000..39bb69f --- /dev/null +++ b/llparse/test/compiler-test.ts @@ -0,0 +1,289 @@ +import { LLParse } from '../src/api'; + +import { + ALPHA, build, NUM, NUM_SELECT, printMatch, printOff, +} from './fixtures'; + +describe('llparse/Compiler', () => { + let p: LLParse; + + beforeEach(() => { + p = new LLParse(); + }); + + it('should compile simple parser', async () => { + const start = p.node('start'); + + start.match(' ', start); + + start.match('HTTP', printOff(p, start)); + + start.select({ + CONNECT: 6, + DELETE: 4, + GET: 1, + HEAD: 0, + OPTIONS: 5, + PATCH: 8, + POST: 2, + PUT: 3, + TRACE: 7, + }, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = await build(p, start, 'simple'); + await binary.check('GET', 'off=3 match=1\n'); + }); + + it('should optimize shallow select', async () => { + const start = p.node('start'); + + start.select(NUM_SELECT, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = await build(p, start, 'shallow'); + await binary.check('012', 'off=1 match=0\noff=2 match=1\noff=3 match=2\n'); + }); + + it('should support key-value select', async () => { + const start = p.node('start'); + + start.select('0', 0, printMatch(p, start)); + start.select('1', 1, printMatch(p, start)); + start.select('2', 2, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = await build(p, start, 'kv-select'); + await binary.check('012', 'off=1 match=0\noff=2 match=1\noff=3 match=2\n'); + }); + + it('should support multi-match', async () => { + const start = p.node('start'); + + start.match([ ' ', '\t', '\r', '\n' ], start); + + start.select({ + A: 0, + B: 1, + }, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = await build(p, start, 'multi-match'); + await binary.check( + 'A B\t\tA\r\nA', + 'off=1 match=0\noff=3 match=1\noff=6 match=0\noff=9 match=0\n'); + }); + + it('should support numeric-match', async () => { + const start = p.node('start'); + + start.match(32, start); + + start.select({ + A: 0, + B: 1, + }, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = await build(p, start, 'multi-match'); + await binary.check( + 'A B A A', + 'off=1 match=0\noff=3 match=1\noff=6 match=0\noff=9 match=0\n'); + }); + + it('should support custom state properties', async () => { + const start = p.node('start'); + const error = p.error(3, 'Invalid word'); + + p.property('i8', 'custom'); + + const second = p.invoke(p.code.load('custom'), { + 0: p.invoke(p.code.match('llparse__print_zero'), { 0: start }, error), + 1: p.invoke(p.code.match('llparse__print_one'), { 0: start }, error), + }, error); + + start + .select({ + 0: 0, + 1: 1, + }, p.invoke(p.code.store('custom'), second)) + .otherwise(error); + + const binary = await build(p, start, 'custom-prop'); + await binary.check('0110', 'off=1 0\noff=2 1\noff=3 1\noff=4 0\n'); + }); + + it('should return error code/reason', async () => { + const start = p.node('start'); + + start.match('a', start); + start.otherwise(p.error(42, 'some reason')); + + const binary = await build(p, start, 'error'); + await binary.check('aab', 'off=2 error code=42 reason="some reason"\n'); + }); + + it('should not merge `.match()` with `.peek()`', async () => { + const maybeCr = p.node('maybeCr'); + const lf = p.node('lf'); + + maybeCr.peek('\n', lf); + maybeCr.match('\r', lf); + maybeCr.otherwise(p.error(1, 'error')); + + lf.match('\n', printOff(p, maybeCr)); + lf.otherwise(p.error(2, 'error')); + + const binary = await build(p, maybeCr, 'no-merge'); + await binary.check('\r\n\n', 'off=2\noff=3\n'); + }); + + describe('`.match()`', () => { + it('should compile to a single-bit table-lookup node', async () => { + const start = p.node('start'); + + start + .match(ALPHA, start) + .skipTo(printOff(p, start)); + + // TODO(indutny): validate compilation result? + const binary = await build(p, start, 'match-bit-check'); + await binary.check('pecan.is.dead.', 'off=6\noff=9\noff=14\n'); + }); + + it('should compile to a multi-bit table-lookup node', async () => { + const start = p.node('start'); + const another = p.node('another'); + + start + .match(ALPHA, start) + .peek(NUM, another) + .skipTo(printOff(p, start)); + + another + .match(NUM, another) + .otherwise(start); + + // TODO(indutny): validate compilation result? + const binary = await build(p, start, 'match-multi-bit-check'); + await binary.check('pecan.135.is.dead.', + 'off=6\noff=10\noff=13\noff=18\n'); + }); + + it('should not overflow on signed char in table-lookup node', async () => { + const start = p.node('start'); + + start + .match(ALPHA, start) + .match([ 0xc3, 0xbc ], start) + .skipTo(printOff(p, start)); + + // TODO(indutny): validate compilation result? + const binary = await build(p, start, 'match-bit-check'); + await binary.check('Düsseldorf.', 'off=12\n'); + }); + + it('should match single quotes and forward slashes', async () => { + const start = p.node('start'); + + start + .match('\'', printOff(p, start)) + .match('\\', printOff(p, start)) + .otherwise(p.error(3, 'Invalid char')); + + // TODO(indutny): validate compilation result? + const binary = await build(p, start, 'escape-char'); + await binary.check('\\\'', 'off=1\noff=2\n'); + }); + + it('should hit SSE4.2 optimization for table-lookup', async () => { + const start = p.node('start'); + + start + .match(ALPHA, start) + .skipTo(printOff(p, start)); + + // TODO(indutny): validate compilation result? + const binary = await build(p, start, 'match-bit-check-sse'); + await binary.check('abcdabcdabcdabcdabcdabcdabcd.abcd.', + 'off=29\noff=34\n'); + }); + + it('should compile overlapping matches', async () => { + const start = p.node('start'); + + start.select({ + aa: 1, + aab: 2, + }, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = await build(p, start, 'overlapping-matches'); + await binary.check('aaaabaa', 'off=2 match=1\noff=5 match=2\n'); + }); + }); + + describe('`.peek()`', () => { + it('should not advance position', async () => { + const start = p.node('start'); + const ab = p.node('ab'); + const error = p.error(3, 'Invalid word'); + + start + .peek([ 'a', 'b' ], ab) + .otherwise(error); + + ab + .match([ 'a', 'b' ], printOff(p, start)) + .otherwise(error); + + const binary = await build(p, start, 'peek'); + await binary.check('ab', 'off=1\noff=2\n'); + }); + }); + + describe('`.otherwise()`', () => { + it('should not advance position by default', async () => { + const a = p.node('a'); + const b = p.node('b'); + + a + .match('A', a) + .otherwise(b); + + b + .match('B', printOff(p, b)) + .skipTo(a); + + const binary = await build(p, a, 'otherwise-noadvance'); + await binary.check('AABAB', 'off=3\noff=5\n'); + }); + + it('should advance when it is `.skipTo()`', async () => { + const start = p.node('start'); + + start + .match(' ', printOff(p, start)) + .skipTo(start); + + const binary = await build(p, start, 'otherwise-skip'); + await binary.check('HELLO WORLD', 'off=6\n'); + }); + + it('should skip everything with `.skipTo()`', async () => { + const start = p.node('start'); + + start + .skipTo(start); + + const binary = await build(p, start, 'all-skip'); + await binary.check('HELLO WORLD', '\n'); + }); + }); +}); diff --git a/llparse/test/consume-test.ts b/llparse/test/consume-test.ts new file mode 100644 index 0000000..f9fb383 --- /dev/null +++ b/llparse/test/consume-test.ts @@ -0,0 +1,69 @@ +import * as assert from 'assert'; + +import { LLParse } from '../src/api'; + +import { build, NUM_SELECT, printMatch, printOff } from './fixtures'; + +describe('llparse/consume', () => { + let p: LLParse; + + beforeEach(() => { + p = new LLParse(); + }); + + it('should consume bytes with i8 field', async () => { + p.property('i8', 'to_consume'); + + const start = p.node('start'); + const consume = p.consume('to_consume'); + + start.select(NUM_SELECT, p.invoke(p.code.store('to_consume'), consume)); + + start + .otherwise(p.error(1, 'unexpected')); + + consume + .otherwise(printOff(p, start)); + + const binary = await build(p, start, 'consume'); + await binary.check('3aaa2bb1a01b', 'off=4\noff=7\noff=9\noff=10\noff=12\n'); + }); + + it('should consume bytes with i64 field', async () => { + p.property('i64', 'to_consume'); + + const start = p.node('start'); + const consume = p.consume('to_consume'); + + start.select(NUM_SELECT, p.invoke(p.code.store('to_consume'), consume)); + + start + .otherwise(p.error(1, 'unexpected')); + + consume + .otherwise(printOff(p, start)); + + const binary = await build(p, start, 'consume-i64'); + await binary.check('3aaa2bb1a01b', 'off=4\noff=7\noff=9\noff=10\noff=12\n'); + }); + + it('should consume bytes with untruncated i64 field', async () => { + p.property('i64', 'to_consume'); + + const start = p.node('start'); + const consume = p.consume('to_consume'); + + start + .select( + NUM_SELECT, + p.invoke(p.code.mulAdd('to_consume', { base: 10 }), start) + ) + .skipTo(consume); + + consume + .otherwise(printOff(p, start)); + + const binary = await build(p, start, 'consume-untruncated-i64'); + await binary.check('4294967297.xxxxxxxx', '\n'); + }); +}); diff --git a/llparse/test/fixtures/extra.c b/llparse/test/fixtures/extra.c new file mode 100644 index 0000000..79cdff9 --- /dev/null +++ b/llparse/test/fixtures/extra.c @@ -0,0 +1,84 @@ +#include "fixture.h" + +int llparse__print_zero(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + llparse__print(p, endp, "0"); + return 0; +} + + +int llparse__print_one(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + llparse__print(p, endp, "1"); + return 0; +} + + +int llparse__print_off(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + llparse__print(p, endp, ""); + return 0; +} + + +int llparse__print_match(llparse_t* s, const char* p, const char* endp, + int value) { + if (llparse__in_bench) + return 0; + llparse__print(p, endp, "match=%d", value); + return 0; +} + + +int llparse__on_dot(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + return llparse__print_span("dot", p, endp); +} + + +int llparse__on_dash(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + return llparse__print_span("dash", p, endp); +} + + +int llparse__on_underscore(llparse_t* s, const char* p, + const char* endp) { + if (llparse__in_bench) + return 0; + return llparse__print_span("underscore", p, endp); +} + + +/* A span callback, really */ +int llparse__please_fail(llparse_t* s, const char* p, const char* endp) { + s->reason = "please fail"; + if (llparse__in_bench) + return 1; + return 1; +} + + +/* A span callback, really */ +static int llparse__pause_once_counter; + +int llparse__pause_once(llparse_t* s, const char* p, const char* endp) { + if (!llparse__in_bench) + llparse__print_span("pause", p, endp); + + if (llparse__pause_once_counter != 0) + return 0; + llparse__pause_once_counter = 1; + + return LLPARSE__ERROR_PAUSE; +} + + +int llparse__test_init() { + llparse__pause_once_counter = 0; +} diff --git a/llparse/test/fixtures/index.ts b/llparse/test/fixtures/index.ts new file mode 100644 index 0000000..d8a7336 --- /dev/null +++ b/llparse/test/fixtures/index.ts @@ -0,0 +1,52 @@ +import { source } from 'llparse-frontend'; +import { Fixture, FixtureResult } from 'llparse-test-fixture'; +import * as path from 'path'; + +import { LLParse } from '../../src/api'; + +export { ERROR_PAUSE } from 'llparse-test-fixture'; + +const fixtures = new Fixture({ + buildDir: path.join(__dirname, '..', 'tmp'), + extra: [ + '-msse4.2', + '-DLLPARSE__TEST_INIT=llparse__test_init', + path.join(__dirname, 'extra.c'), + ], +}); + +export function build(llparse: LLParse, node: source.node.Node, outFile: string) + : Promise<FixtureResult> { + return fixtures.build(llparse.build(node, { + c: { + header: outFile, + }, + }), outFile); +} + +export function printMatch(p: LLParse, next: source.node.Node) + : source.node.Node { + const code = p.code.value('llparse__print_match'); + const res = p.invoke(code, next); + return res; +} + +export function printOff(p: LLParse, next: source.node.Node): source.node.Node { + const code = p.code.match('llparse__print_off'); + return p.invoke(code, next); +} + +export const NUM_SELECT: { readonly [key: string]: number } = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, +}; + +export const NUM: ReadonlyArray<string> = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', +]; + +export const ALPHA: ReadonlyArray<string> = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', +]; diff --git a/llparse/test/resumption-test.ts b/llparse/test/resumption-test.ts new file mode 100644 index 0000000..438b7fd --- /dev/null +++ b/llparse/test/resumption-test.ts @@ -0,0 +1,55 @@ +import * as assert from 'assert'; + +import { LLParse } from '../src/api'; + +import { build, ERROR_PAUSE, printMatch, printOff } from './fixtures'; + +describe('llparse/resumption', () => { + let p: LLParse; + + beforeEach(() => { + p = new LLParse(); + }); + + it('should resume after span end pause', async () => { + const start = p.node('start'); + const a = p.node('a'); + const span = p.span(p.code.span('llparse__pause_once')); + + start + .peek('a', span.start(a)) + .skipTo(start); + + a + .match('a', a) + .otherwise(span.end(start)); + + const binary = await build(p, start, 'resume-span'); + + await binary.check('baaab', + new RegExp( + '^(' + + 'off=\\d+ pause\\noff=1 len=3 span\\[pause\\]="aaa"' + + '|' + + 'off=1 len=3 span\\[pause\\]="aaa"\noff=4 pause' + + ')\\n$' + , 'g')); + }); + + it('should resume after `pause` node', async () => { + const start = p.node('start'); + const pause = p.pause(ERROR_PAUSE, 'paused'); + + start + .match('p', pause) + .skipTo(start); + + pause + .otherwise(printOff(p, start)); + + const binary = await build(p, start, 'resume-pause'); + + await binary.check('..p....p..', + 'off=3 pause\noff=3\noff=8 pause\noff=8\n'); + }); +}); diff --git a/llparse/test/span-test.ts b/llparse/test/span-test.ts new file mode 100644 index 0000000..b01ad51 --- /dev/null +++ b/llparse/test/span-test.ts @@ -0,0 +1,107 @@ +import * as assert from 'assert'; + +import { LLParse } from '../src/api'; + +import { build, printMatch, printOff } from './fixtures'; + +describe('llparse/spans', () => { + let p: LLParse; + + beforeEach(() => { + p = new LLParse(); + }); + + it('should invoke span callback', async () => { + const start = p.node('start'); + const dot = p.node('dot'); + const dash = p.node('dash'); + const underscore = p.node('underscore'); + + const span = { + dash: p.span(p.code.span('llparse__on_dash')), + dot: p.span(p.code.span('llparse__on_dot')), + underscore: p.span(p.code.span('llparse__on_underscore')), + }; + + start.otherwise(span.dot.start(dot)); + + dot + .match('.', dot) + .peek('-', span.dash.start(dash)) + .peek('_', span.underscore.start(underscore)) + .skipTo(span.dot.end(start)); + + dash + .match('-', dash) + .otherwise(span.dash.end(dot)); + + underscore + .match('_', underscore) + .otherwise(span.underscore.end(dot)); + + const binary = await build(p, start, 'span'); + await binary.check('..--..__..', + 'off=2 len=2 span[dash]="--"\n' + + 'off=6 len=2 span[underscore]="__"\n' + + 'off=0 len=10 span[dot]="..--..__.."\n'); + }); + + it('should return error', async () => { + const start = p.node('start'); + const dot = p.node('dot'); + + const span = { + pleaseFail: p.span(p.code.span('llparse__please_fail')), + }; + + start.otherwise(span.pleaseFail.start(dot)); + + dot + .match('.', dot) + .skipTo(span.pleaseFail.end(start)); + + const binary = await build(p, start, 'span-error'); + + await binary.check( + '....a', + /off=\d+ error code=1 reason="please fail"\n/); + }); + + it('should return error at `executeSpans()`', async () => { + const start = p.node('start'); + const dot = p.node('dot'); + + const span = { + pleaseFail: p.span(p.code.span('llparse__please_fail')), + }; + + start.otherwise(span.pleaseFail.start(dot)); + + dot + .match('.', dot) + .skipTo(span.pleaseFail.end(start)); + + const binary = await build(p, start, 'span-error-execute'); + + await binary.check( + '.........', + /off=9 error code=1 reason="please fail"\n/, { scan: 100 }); + }); + + it('should not invoke spurious span callback', async () => { + const start = p.node('start'); + const dot = p.node('dot'); + const span = p.span(p.code.span('llparse__on_dot')); + + start + .match('hello', span.start(dot)) + .skipTo(start); + + dot + .match('.', dot) + .skipTo(span.end(start)); + + const binary = await build(p, start, 'span-spurious'); + await binary.check('hello', [ '' ]); + }); +}); diff --git a/llparse/test/transform-test.ts b/llparse/test/transform-test.ts new file mode 100644 index 0000000..d30381e --- /dev/null +++ b/llparse/test/transform-test.ts @@ -0,0 +1,41 @@ +import * as assert from 'assert'; + +import { LLParse } from '../src/api'; + +import { build, printMatch, printOff } from './fixtures'; + +describe('llparse/transform', () => { + let p: LLParse; + + beforeEach(() => { + p = new LLParse(); + }); + + it('should apply transformation before the match', async () => { + const start = p.node('start'); + + start + .transform(p.transform.toLowerUnsafe()) + .match('connect', printOff(p, start)) + .match('close', printOff(p, start)) + .otherwise(p.error(1, 'error')); + + const binary = await build(p, start, 'transform-lower'); + await binary.check('connectCLOSEcOnNeCt', 'off=7\noff=12\noff=19\n'); + }); + + it('should apply safe `toLower()` transformation', async () => { + const start = p.node('start'); + + start + .transform(p.transform.toLower()) + .select({ + 'a-b': 1, + 'a\rb': 2, + }, printMatch(p, start)) + .otherwise(p.error(1, 'error')); + + const binary = await build(p, start, 'transform-safe-lower'); + await binary.check('A-ba\rB', 'off=3 match=1\noff=6 match=2\n'); + }); +}); diff --git a/llparse/tsconfig.json b/llparse/tsconfig.json new file mode 100644 index 0000000..01ec7c2 --- /dev/null +++ b/llparse/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2017", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./lib", + "declaration": true, + "pretty": true, + "sourceMap": true + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/llparse/tslint.json b/llparse/tslint.json new file mode 100644 index 0000000..24fec09 --- /dev/null +++ b/llparse/tslint.json @@ -0,0 +1,16 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-bitwise": null, + "max-line-length": [true, 80], + "max-classes-per-file": [true, 1, "exclude-class-expressions"], + "quotemark": [ + true, "single", "avoid-escape", "avoid-template" + ] + }, + "rulesDirectory": [] +} |