diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..595cf53 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,31 @@ +module.exports = { + 'env': { + 'browser': false, + 'commonjs': true, + 'es6': true, + 'node': true + }, + 'extends': 'eslint:recommended', + 'rules': { + 'max-len': [ 2, { + 'code': 80, + 'ignoreComments': true + } ], + 'indent': [ + 'error', + 2 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ] + } +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 20069e8..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: CI - -on: [push, pull_request] - -env: - CI: true - -permissions: - contents: read - -jobs: - test: - name: Run tests - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - macos-latest - - ubuntu-latest - - windows-latest - steps: - - name: Install clang for Windows - if: runner.os == 'Windows' - run: | - iwr -useb get.scoop.sh -outfile 'install.ps1' - .\install.ps1 -RunAsAdmin - scoop install llvm --global - - # Scoop modifies the PATH so we make the modified PATH global. - echo $env:PATH >> $env:GITHUB_PATH - - - name: Fetch code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 1 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 'lts/*' - cache: 'npm' - cache-dependency-path: package-lock.json - - - name: Install dependencies - run: npm ci --ignore-scripts - - - name: Run tests - run: npm run test - - lint: - name: Run Lint - runs-on: ubuntu-latest - steps: - - name: Fetch code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 1 - - - name: Restore node_modules cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Install dependencies - run: npm ci --ignore-scripts - - - name: Run lint command - run: npm run lint diff --git a/.gitignore b/.gitignore index 88b2771..d4fa5a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,13 @@ node_modules/ npm-debug.log lib/ -test/tmp/ +test/tmp +examples/http/http +*.dSYM +*.ll +*.bc +*.o +*.h +*.s +main +1 diff --git a/CNAME b/CNAME deleted file mode 100644 index e39566e..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -llparse.org \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 4c21140..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,4 +0,0 @@ -# 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/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 6c1512d..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,22 +0,0 @@ -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/README.md b/README.md index afbe4aa..53226e3 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # llparse -[![Build Status](https://secure.travis-ci.org/nodejs/llparse.svg)](http://travis-ci.org/nodejs/llparse) +[![Build Status](https://secure.travis-ci.org/indutny/llparse.svg)](http://travis-ci.org/indutny/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. +An API for compiling an incremental parser into [LLVM bitcode][3]. + +**NOTE: The resulting bitcode works only on a trunk version of `clang` now, +other versions are broken. The required fixes will be released in +`clang` 6.0.1** ## Usage -```ts -import { LLParse } from 'llparse'; +```js +'use strict'; -const p = new LLParse('http_parser'); +const p = require('llparse').create('http_parser'); const method = p.node('method'); const beforeUrl = p.node('before_url'); @@ -50,9 +54,9 @@ http .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('----- BITCODE -----'); +console.log(artifacts.bitcode); // buffer +console.log('----- BITCODE END -----'); console.log('----- HEADER -----'); console.log(artifacts.header); console.log('----- HEADER END -----'); @@ -62,7 +66,7 @@ console.log('----- HEADER END -----'); This software is licensed under the MIT License. -Copyright Fedor Indutny, 2020. +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 @@ -83,4 +87,7 @@ 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://reviews.llvm.org/D43729 +[1]: https://reviews.llvm.org/D43708 +[2]: https://reviews.llvm.org/D43695 [3]: https://llvm.org/docs/LangRef.html diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 1885487..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-midnight \ No newline at end of file diff --git a/bench/.gitignore b/bench/.gitignore new file mode 100644 index 0000000..2e27c28 --- /dev/null +++ b/bench/.gitignore @@ -0,0 +1 @@ +!*.ll diff --git a/bench/bitmap_vs_switch/bitmap.c b/bench/bitmap_vs_switch/bitmap.c new file mode 100644 index 0000000..166a606 --- /dev/null +++ b/bench/bitmap_vs_switch/bitmap.c @@ -0,0 +1,53 @@ +#include + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + + +uint8_t llparse__bench_bitmap(const char* p, const char* endp) { + for (; p != endp; p++) { + unsigned char c; + + c = (unsigned char) *p; + if (!BIT_AT(normal_url_char, c)) + return 0; + } + return 1; +} diff --git a/bench/bitmap_vs_switch/main.c b/bench/bitmap_vs_switch/main.c new file mode 100644 index 0000000..29272c5 --- /dev/null +++ b/bench/bitmap_vs_switch/main.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +/* Please don't use -flto, it breaks my heart... and benchmarks */ +uint8_t llparse__bench_switch(const char* p, const char* endp); +uint8_t llparse__bench_bitmap(const char* p, const char* endp); + +/* 8 gb */ +static const int64_t kBytes = 8589934592LL; + + +static void bench(int llvm, const char* input) { + const char* endp; + int len; + + struct timeval start; + struct timeval end; + double bw; + double time; + int64_t i; + int64_t iterations; + + len = strlen(input); + endp = input + len; + iterations = kBytes / (int64_t) len; + + gettimeofday(&start, NULL); + if (llvm) { + for (i = 0; i < iterations; i++) + llparse__bench_switch(input, endp); + } else { + for (i = 0; i < iterations; i++) + llparse__bench_bitmap(input, endp); + } + + gettimeofday(&end, NULL); + + time = (end.tv_sec - start.tv_sec); + time += (double) (end.tv_usec - start.tv_usec) * 1e-6; + bw = (double) kBytes / time; + + fprintf(stdout, "%s: %.2f mb | %.2f mb/s | %.2f s\n", + llvm ? "switch" : "bitmap", + (double) kBytes / (1024 * 1024), + bw / (1024 * 1024), + time); +} + + +int main(int argc, const char** argv) { + if (argc < 2) + return -1; + + bench(0, argv[1]); + bench(1, argv[1]); + return 0; +} diff --git a/bench/bitmap_vs_switch/switch.ll b/bench/bitmap_vs_switch/switch.ll new file mode 100644 index 0000000..8428a40 --- /dev/null +++ b/bench/bitmap_vs_switch/switch.ll @@ -0,0 +1,25 @@ +define i8 @llparse__bench_switch(i8* %p, i8* %endp) nounwind minsize ssp uwtable { +start: + br label %loop + +loop: + %current = phi i8* [ %p, %start ], [ %next, %match ] + ; --- Prologue --- + ; if (pos != endpos) + %i0 = icmp ne i8* %current, %endp + br i1 %i0, label %has_data, label %no_data + +has_data: + ; node.Single + %c = load i8, i8* %current + switch i8 %c, label %no_match [ i8 9 , label %match i8 12 , label %match i8 33 , label %match i8 34 , label %match i8 36 , label %match i8 37 , label %match i8 38 , label %match i8 39 , label %match i8 40 , label %match i8 41 , label %match i8 42 , label %match i8 43 , label %match i8 44 , label %match i8 45 , label %match i8 46 , label %match i8 47 , label %match i8 48 , label %match i8 49 , label %match i8 50 , label %match i8 51 , label %match i8 52 , label %match i8 53 , label %match i8 54 , label %match i8 55 , label %match i8 56 , label %match i8 57 , label %match i8 58 , label %match i8 59 , label %match i8 60 , label %match i8 61 , label %match i8 62 , label %match i8 64 , label %match i8 65 , label %match i8 66 , label %match i8 67 , label %match i8 68 , label %match i8 69 , label %match i8 70 , label %match i8 71 , label %match i8 72 , label %match i8 73 , label %match i8 74 , label %match i8 75 , label %match i8 76 , label %match i8 77 , label %match i8 78 , label %match i8 79 , label %match i8 80 , label %match i8 81 , label %match i8 82 , label %match i8 83 , label %match i8 84 , label %match i8 85 , label %match i8 86 , label %match i8 87 , label %match i8 88 , label %match i8 89 , label %match i8 90 , label %match i8 91 , label %match i8 92 , label %match i8 93 , label %match i8 94 , label %match i8 95 , label %match i8 96 , label %match i8 97 , label %match i8 98 , label %match i8 99 , label %match i8 100 , label %match i8 101 , label %match i8 102 , label %match i8 103 , label %match i8 104 , label %match i8 105 , label %match i8 106 , label %match i8 107 , label %match i8 108 , label %match i8 109 , label %match i8 110 , label %match i8 111 , label %match i8 112 , label %match i8 113 , label %match i8 114 , label %match i8 115 , label %match i8 116 , label %match i8 117 , label %match i8 118 , label %match i8 119 , label %match i8 120 , label %match i8 121 , label %match i8 122 , label %match i8 123 , label %match i8 125 , label %match i8 126 , label %match ] +no_match: + ret i8 0 +match: + ; next = pos + 1 + %next = getelementptr inbounds i8, i8* %current, i32 1 + br label %loop + +no_data: + ret i8 1 +} diff --git a/eslint.json b/eslint.json deleted file mode 100644 index 1462b83..0000000 --- a/eslint.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@stylistic/recommended-extends" - ], - "rules": { - "max-len": [ - "error", - { - "code": 120 - } - ], - "no-console": "error", - "@typescript-eslint/no-unused-vars": "error", - "@stylistic/array-bracket-spacing": [ - "error", - "always" - ], - "@stylistic/operator-linebreak": [ - "error", - "after" - ], - "@stylistic/brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "@stylistic/member-delimiter-style": [ - "error", - { - "overrides": { - "interface": { - "multiline": { - "delimiter": "semi", - "requireLast": true - } - } - } - } - ], - "indent": "off", - "@stylistic/indent": [ - "error", - 2, - { - "SwitchCase": 1, - "FunctionDeclaration": { - "parameters": "first" - }, - "FunctionExpression": { - "parameters": "first" - } - } - ], - "semi": "off", - "@stylistic/semi": [ - "error", - "always" - ] - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": [ - "tsconfig.eslint.json" - ], - "sourceType": "module", - "ecmaFeatures": { - "jsx": true, - "modules": true, - "experimentalObjectRestSpread": true - } - } -} diff --git a/examples/http/.gitignore b/examples/http/.gitignore deleted file mode 100644 index fcfe02e..0000000 --- a/examples/http/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -http -*.c -*.ll -*.h -*.o -*.dSYM diff --git a/examples/http/Makefile b/examples/http/Makefile index 323d2e3..c393ebd 100644 --- a/examples/http/Makefile +++ b/examples/http/Makefile @@ -3,9 +3,9 @@ CC ?= clang all: http http: main.c http_parser.bc - $(CC) -g3 -flto -Os -fvisibility=hidden -Wall -I. http_parser.c main.c -o $@ + $(CC) -g3 -flto -Os -fvisibility=hidden -Wall -I. http_parser.bc main.c -o $@ -http_parser.bc: index.ts - npx ts-node $< +http_parser.bc: index.js + node $< .PHONY = all diff --git a/examples/http/index.ts b/examples/http/index.js similarity index 88% rename from examples/http/index.ts rename to examples/http/index.js index dc7f28a..d1e6d23 100644 --- a/examples/http/index.ts +++ b/examples/http/index.js @@ -1,6 +1,6 @@ -import { LLParse } from '../../src/api'; +'use strict'; -const p = new LLParse('http_parser'); +const p = require('../../').create('http_parser'); const method = p.node('method'); const beforeUrl = p.node('before_url'); @@ -48,4 +48,4 @@ 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); +fs.writeFileSync(path.join(__dirname, 'http_parser.bc'), artifacts.bitcode); diff --git a/examples/http/main.c b/examples/http/main.c index 4721a19..358df99 100644 --- a/examples/http/main.c +++ b/examples/http/main.c @@ -5,7 +5,7 @@ #include "http_parser.h" -int on_url(http_parser_t* s, const char* p, const char* endp) { +int on_url(http_parser_state_t* s, const char* p, const char* endp) { if (p == endp) return 0; @@ -15,14 +15,14 @@ int on_url(http_parser_t* s, const char* p, const char* endp) { } -int on_complete(http_parser_t* s, const char* p, const char* endp) { +int on_complete(http_parser_state_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_state_t s; http_parser_init(&s); diff --git a/package-lock.json b/package-lock.json index 25a6682..606116e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2779 +1,1361 @@ { "name": "llparse", - "version": "7.3.0", - "lockfileVersion": 3, + "version": "3.0.0", + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "llparse", - "version": "7.3.0", - "license": "MIT", - "dependencies": { - "debug": "^4.2.0", - "llparse-frontend": "^3.0.0" - }, - "devDependencies": { - "@stylistic/eslint-plugin": "^2.6.1", - "@types/debug": "^4.1.5", - "@types/mocha": "^8.0.3", - "@types/node": "^20.11.16", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "eslint": "^8.56.0", - "esm": "^3.2.25", - "llparse-test-fixture": "^5.0.1", - "mocha": "^9.2.2", - "ts-node": "^9.0.0", - "typescript": "^5.0.3" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/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, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/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, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@stylistic/eslint-plugin": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.13.0.tgz", - "integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^8.13.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.17.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", - "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", - "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/type-utils": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", - "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", - "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", - "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", - "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", - "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", - "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", - "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/binary-search": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", - "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==", - "license": "CC0-1.0" - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/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, - "license": "ISC" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/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, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/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, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.x" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/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, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": { + "@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, + "@types/node": { + "version": "9.4.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.7.tgz", + "integrity": "sha512-4Ba90mWNx8ddbafuyGGwjkZMigi+AWfYLSDCpovwsE63ia8w93r3oJ8PIAQc3y8U+XHcnMOHPIzNe3o438Ywcw==", + "dev": true + }, + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "requires": { + "acorn": "3.3.0" }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/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==", + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, - "node_modules/is-extglob": { + "ajv-keywords": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true }, - "node_modules/is-fullwidth-code-point": { + "ansi-escapes": { "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "sprintf-js": "1.0.3" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "requires": { + "array-uniq": "1.0.3" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true }, - "node_modules/json-stable-stringify-without-jsonify": { + "arrify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/llparse": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/llparse/-/llparse-7.2.0.tgz", - "integrity": "sha512-sKxBvM2PJ84FpSUUuup/mMFuODM2pG8zcVGJSmMuq+2fdBWK4KwbogV1unnbki1jwYognRvwPR7svsX7gFKzAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.2.0", - "llparse-frontend": "^3.0.0" - } - }, - "node_modules/llparse-builder": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz", - "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==", - "license": "MIT", - "dependencies": { - "@types/debug": "4.1.5 ", - "binary-search": "^1.3.6", - "debug": "^4.2.0" - } - }, - "node_modules/llparse-builder/node_modules/@types/debug": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", - "license": "MIT" - }, - "node_modules/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==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.6", - "llparse-builder": "^1.5.2" - } - }, - "node_modules/llparse-frontend/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/llparse-test-fixture": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/llparse-test-fixture/-/llparse-test-fixture-5.1.0.tgz", - "integrity": "sha512-HeRay3HmzUPgVXClUGTsXPWhLSibcgW8VQVjPVuT4q6oRXIdjjh886vgXZ69bIQ3XHfy4bE3BhP/tm44oFE8kQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "llparse": "^7.0.0", - "yargs": "^15.4.1" - } - }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/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, - "license": "ISC" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bitcode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bitcode/-/bitcode-1.1.0.tgz", + "integrity": "sha512-uA2u1LsyaucwRM5Rlns/ydURJ4lcaGK4PJ+XaXSFu7VcUA8obwPvYglRNlHarg0E26Xnq7rE68Arqs9plexpag==", + "requires": { + "bitcode-builder": "1.1.5" } }, - "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } + "bitcode-builder": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bitcode-builder/-/bitcode-builder-1.1.5.tgz", + "integrity": "sha512-OGOdEJoTUpYgwWK6FFfM6bnIXwAQTR/putCekJdLg1GwQys9wpL8kEoSUou4pv+fRecsnvAEEGDQrjMS1S109w==" }, - "node_modules/mocha/node_modules/brace-expansion": { + "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, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", + "requires": { + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/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, - "license": "MIT" - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } + "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 }, - "node_modules/mocha/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } + "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 }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" + "requires": { + "callsites": "0.2.0" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "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": { + "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.1" + } + }, + "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" + } + } } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" + "requires": { + "restore-cursor": "2.0.0" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "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.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.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "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 }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "concat-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.1.tgz", + "integrity": "sha512-gslSSJx03QKa59cIKqeJO9HQ/WZMotvYJCuaUULrLpjj8oG40kV2Z+gz82pVxlTkOADi4PJxQPPfhl1ELYrrXw==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5", + "typedarray": "0.0.6" } }, - "node_modules/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==", + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.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 + }, + "eslint": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.2.tgz", + "integrity": "sha512-qy4i3wODqKMYfz9LUI8N2qYDkHkoieTbiHpMrYUI/WbjhXJQr7lI4VngixTgaG+yHX+NBCv7nW4hA0ShbvaNKw==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.3.2", + "concat-stream": "1.6.1", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.1.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "11.3.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.11.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.5.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" + "requires": { + "acorn": "5.5.3", + "acorn-jsx": "3.0.1" } }, - "node_modules/path-exists": { + "esprima": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "estraverse": "4.2.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "estraverse": "4.2.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" } }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "requires": { + "escape-string-regexp": "1.0.5" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "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.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz", + "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" + "requires": { + "ansi-regex": "2.1.1" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" } }, - "node_modules/require-main-filename": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.3.2", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.1.0", + "figures": "2.0.0", + "lodash": "4.17.5", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { "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, - "license": "ISC" + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "requires": { + "is-path-inside": "1.0.1" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "requires": { + "path-is-inside": "1.0.2" } }, - "node_modules/rimraf": { + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "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 + }, + "js-tokens": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" } }, - "node_modules/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, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "llparse-test-fixture": { + "version": "git://github.com/indutny/llparse-test-fixture.git#93c555d7cfced42ad7bf531bf981a9ca5ead66e8", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "requires": { + "async": "2.6.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "lru-cache": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "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, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "requires": { + "brace-expansion": "1.1.11" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "requires": { + "minimist": "0.0.8" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "mocha": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.4.tgz", + "integrity": "sha512-nMOpAPFosU1B4Ix1jdhx5e3q7XO55ic5a8cgYvW27CequcEY+BabS0kUVL1Cw1V5PuVHZWeNRWFLmEPexo79VA==", "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "wrappy": "1.0.2" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "mimic-fn": "1.2.0" } }, - "node_modules/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==", + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, - "node_modules/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==", + "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-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "requires": { + "pinkie": "2.0.4" } }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, - "license": "MIT", - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" } }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" + "requires": { + "path-parse": "1.0.5" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "glob": "7.1.2" } }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "requires": { + "is-promise": "2.1.0" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "requires": { + "rx-lite": "4.0.8" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "license": "ISC" + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "requires": { + "shebang-regex": "1.0.0" } }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true, - "license": "Apache-2.0" + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, - "license": "ISC" + "requires": { + "is-fullwidth-code-point": "2.0.0" + } }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "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 }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "source-map-support": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", + "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", "dev": true, - "license": "MIT", - "dependencies": { - "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" - }, - "engines": { - "node": ">=8" + "requires": { + "source-map": "0.6.1" } }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "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, - "license": "ISC", - "engines": { - "node": ">=10" + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" + "requires": { + "safe-buffer": "5.1.1" } }, - "node_modules/yargs-unparser/node_modules/decamelize": { + "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "requires": { + "ansi-regex": "3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } } }, - "node_modules/yargs/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.3.2", + "lodash": "4.17.5", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" } }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "os-tmpdir": "1.0.2" } }, - "node_modules/yargs/node_modules/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, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "ts-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-5.0.1.tgz", + "integrity": "sha512-XK7QmDcNHVmZkVtkiwNDWiERRHPyU8nBqZB1+iv2UhOG0q3RQ9HsZ2CMqISlFbxjrYFGfG2mX7bW4dAyxBVzUw==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "chalk": "2.3.2", + "diff": "3.5.0", + "make-error": "1.3.4", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.5.3", + "yn": "2.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, - "node_modules/yargs/node_modules/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, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", + "dev": true + }, + "tslint": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.3.2", + "commander": "2.15.0", + "diff": "3.5.0", + "glob": "7.1.2", + "js-yaml": "3.11.0", + "minimatch": "3.0.4", + "resolve": "1.5.0", + "semver": "5.5.0", + "tslib": "1.9.0", + "tsutils": "2.22.2" + }, + "dependencies": { + "commander": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.0.tgz", + "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==", + "dev": true + } } }, - "node_modules/yargs/node_modules/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==", + "tsutils": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.22.2.tgz", + "integrity": "sha512-u06FUSulCJ+Y8a2ftuqZN6kIGqdP2yJjUPEngXqmdPND4UQfb04igcotH+dw+IFr417yP6muCLE8/5/Qlfnx0w==", "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "requires": { + "tslib": "1.9.0" } }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" + "requires": { + "prelude-ls": "1.1.2" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "requires": { + "isexe": "2.0.0" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "mkdirp": "0.5.1" } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true } } } diff --git a/package.json b/package.json index 5aa7b91..ff063cd 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,48 @@ { "name": "llparse", - "version": "7.3.0", - "description": "Compile incremental parsers to C code", - "main": "lib/api.js", - "types": "lib/api.d.ts", - "files": [ - "lib", - "src" - ], + "version": "3.0.0", + "main": "lib/llparse.js", "scripts": { "build": "tsc", "clean": "rm -rf lib", "prepare": "npm run clean && npm run build", - "lint": "eslint -c eslint.json src/**/*.ts test/**/*.ts", - "lint:fix": "eslint --fix -c eslint.json src/**/*.ts test/**/*.ts", - "mocha": "mocha --timeout=10000 -r ts-node/register/type-check --reporter spec test/*-test.ts", + "lint": "tslint -c tslint.json src/*.ts 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/nodejs/llparse.git" - }, + "files": [ + "lib" + ], "keywords": [ "llparse", - "compiler" + "llvm", + "ir", + "dfa" ], "author": "Fedor Indutny (http://darksi.de/)", "license": "MIT", - "bugs": { - "url": "https://github.com/nodejs/llparse/issues" + "dependencies": { + "bitcode": "^1.1.0", + "debug": "^3.1.0" }, - "homepage": "https://github.com/nodejs/llparse#readme", "devDependencies": { - "@stylistic/eslint-plugin": "^2.6.1", - "@types/debug": "^4.1.5", - "@types/mocha": "^8.0.3", - "@types/node": "^20.11.16", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "eslint": "^8.56.0", - "esm": "^3.2.25", - "llparse-test-fixture": "^5.0.1", - "mocha": "^9.2.2", - "ts-node": "^9.0.0", - "typescript": "^5.0.3" + "@types/mocha": "^2.2.48", + "@types/node": "^9.4.7", + "async": "^2.6.0", + "eslint": "^4.18.2", + "llparse-test-fixture": "git://github.com/indutny/llparse-test-fixture.git", + "mocha": "^5.0.4", + "ts-node": "^5.0.1", + "tslint": "^5.9.1", + "typescript": "^2.7.2" }, - "dependencies": { - "debug": "^4.2.0", - "llparse-frontend": "^3.0.0" - } + "directories": { + "lib": "lib", + "test": "test" + }, + "repository": { + "type": "git", + "url": "gh:indutny/llparse" + }, + "description": "Generating parsers in LLVM IR" } diff --git a/src/api.ts b/src/api.ts deleted file mode 100644 index a34f5bc..0000000 --- a/src/api.ts +++ /dev/null @@ -1,47 +0,0 @@ -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/src/api/code.ts b/src/api/code.ts new file mode 100644 index 0000000..84ea0da --- /dev/null +++ b/src/api/code.ts @@ -0,0 +1,47 @@ +import { code } from '../llparse'; + +export class CodeAPI { + // TODO(indutny): should we allow custom bodies here? + match(name: string): code.Match { + return new code.Match(name); + } + + // TODO(indutny): should we allow custom bodies here? + value(name: string): code.Value { + return new code.Value(name); + } + + span(name: string): code.Span { + return new code.Span(name); + } + + // Helpers + + store(field: string): code.Store { + return new code.Store(field); + } + + load(field: string): code.Load { + return new code.Load(field); + } + + mulAdd(field: string, options: code.IMulAddOptions): code.MulAdd { + return new code.MulAdd(field, options); + } + + update(field: string, value: number): code.Update { + return new code.Update(field, value); + } + + isEqual(field: string, value: number): code.IsEqual { + return new code.IsEqual(field, value); + } + + or(field: string, value: number): code.Or { + return new code.Or(field, value); + } + + test(field: string, value: number): code.Test { + return new code.Test(field, value); + } +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..77006fe --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,2 @@ +export { CodeAPI } from './code'; +export { TransformAPI } from './transform'; diff --git a/src/api/transform.ts b/src/api/transform.ts new file mode 100644 index 0000000..4b0ea5f --- /dev/null +++ b/src/api/transform.ts @@ -0,0 +1,7 @@ +import { transform } from '../internal'; + +export class TransformAPI { + public toLowerUnsafe(): transform.Transform { + return new transform.Transform('to_lower_unsafe'); + } +} diff --git a/src/compiler/header-builder.ts b/src/compiler/header-builder.ts deleted file mode 100644 index 3de9c12..0000000 --- a/src/compiler/header-builder.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as frontend from 'llparse-frontend'; -import source = frontend.source; - -export interface IHeaderBuilderOptions { - readonly prefix: string; - readonly headerGuard?: string; - readonly properties: ReadonlyArray; - readonly spans: ReadonlyArray; -} - -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 \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/src/compiler/index.ts b/src/compiler/index.ts deleted file mode 100644 index 80eb584..0000000 --- a/src/compiler/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -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): 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/src/implementation/c/code/and.ts b/src/implementation/c/code/and.ts deleted file mode 100644 index fdd5434..0000000 --- a/src/implementation/c/code/and.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class And extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - out.push(`${this.field(ctx)} &= ${this.ref.value};`); - out.push('return 0;'); - } -} diff --git a/src/implementation/c/code/base.ts b/src/implementation/c/code/base.ts deleted file mode 100644 index 888330d..0000000 --- a/src/implementation/c/code/base.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; - -export abstract class Code { - protected cachedDecl: string | undefined; - - constructor(public readonly ref: T) { - } - - public abstract build(ctx: Compilation, out: string[]): void; -} diff --git a/src/implementation/c/code/external.ts b/src/implementation/c/code/external.ts deleted file mode 100644 index 494fc5a..0000000 --- a/src/implementation/c/code/external.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Code } from './base'; - -export abstract class External - extends Code { - - 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/src/implementation/c/code/field.ts b/src/implementation/c/code/field.ts deleted file mode 100644 index 51f4439..0000000 --- a/src/implementation/c/code/field.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Code } from './base'; - -export abstract class Field extends Code { - 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/src/implementation/c/code/index.ts b/src/implementation/c/code/index.ts deleted file mode 100644 index bb5a9ea..0000000 --- a/src/implementation/c/code/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -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'; - -class Match extends External {} -class Span extends External {} -class Value extends External {} - -export default { - And, - IsEqual, - Load, - Match, - MulAdd, - Or, - Span, - Store, - Test, - Update, - Value, -}; diff --git a/src/implementation/c/code/is-equal.ts b/src/implementation/c/code/is-equal.ts deleted file mode 100644 index f76c2c1..0000000 --- a/src/implementation/c/code/is-equal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class IsEqual extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - out.push(`return ${this.field(ctx)} == ${this.ref.value};`); - } -} diff --git a/src/implementation/c/code/load.ts b/src/implementation/c/code/load.ts deleted file mode 100644 index b913f23..0000000 --- a/src/implementation/c/code/load.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class Load extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - out.push(`return ${this.field(ctx)};`); - } -} diff --git a/src/implementation/c/code/mul-add.ts b/src/implementation/c/code/mul-add.ts deleted file mode 100644 index fd5ce8c..0000000 --- a/src/implementation/c/code/mul-add.ts +++ /dev/null @@ -1,67 +0,0 @@ -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 { - 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/src/implementation/c/code/or.ts b/src/implementation/c/code/or.ts deleted file mode 100644 index 76b16f9..0000000 --- a/src/implementation/c/code/or.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class Or extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - out.push(`${this.field(ctx)} |= ${this.ref.value};`); - out.push('return 0;'); - } -} diff --git a/src/implementation/c/code/store.ts b/src/implementation/c/code/store.ts deleted file mode 100644 index a37d963..0000000 --- a/src/implementation/c/code/store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class Store extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - out.push(`${this.field(ctx)} = ${ctx.matchVar()};`); - out.push('return 0;'); - } -} diff --git a/src/implementation/c/code/test.ts b/src/implementation/c/code/test.ts deleted file mode 100644 index 36126f5..0000000 --- a/src/implementation/c/code/test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class Test extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - const value = this.ref.value; - out.push(`return (${this.field(ctx)} & ${value}) == ${value};`); - } -} diff --git a/src/implementation/c/code/update.ts b/src/implementation/c/code/update.ts deleted file mode 100644 index 89efedf..0000000 --- a/src/implementation/c/code/update.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Field } from './field'; - -export class Update extends Field { - protected doBuild(ctx: Compilation, out: string[]): void { - out.push(`${this.field(ctx)} = ${this.ref.value};`); - out.push('return 0;'); - } -} diff --git a/src/implementation/c/compilation.ts b/src/implementation/c/compilation.ts deleted file mode 100644 index 4cb017a..0000000 --- a/src/implementation/c/compilation.ts +++ /dev/null @@ -1,337 +0,0 @@ -import * as assert from 'assert'; -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, -} 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; - -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> = new Map(); - private readonly blobs: Map = new Map(); - private readonly codeMap: Map> = new Map(); - private readonly matchSequence: - Map = new Map(); - private readonly resumptionTargets: Set = new Set(); - - constructor(public readonly prefix: string, - private readonly properties: ReadonlyArray, - resumptionTargets: ReadonlySet, - 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]; - assert(value !== undefined); - - hex.push(this.toChar(value)); - } - 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): 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('}'); - }); - } - - 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('}'); - }); - } - - public addState(state: string, lines: ReadonlyArray): void { - assert(!this.stateMap.has(state)); - this.stateMap.set(state, lines); - } - - public buildCode(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) - : Code { - const container = code as frontend.ContainerWrap; - return container.get(CONTAINER_KEY); - } - - public unwrapNode(node: WrappedNode): Node { - const container = node as frontend.ContainerWrap; - return container.get(CONTAINER_KEY); - } - - public unwrapTransform(node: frontend.IWrap) - : Transform { - const container = - node as frontend.ContainerWrap; - return container.get(CONTAINER_KEY); - } - - public indent(out: string[], lines: ReadonlyArray, pad: string) { - for (const line of lines) { - out.push(`${pad}${line}`); - } - } - - // MatchSequence cache - - public getMatchSequence( - transform: frontend.IWrap, 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; - } - - public toChar(value: number): string { - const ch = String.fromCharCode(value); - // `'`, `\` - if (value === 0x27 || value === 0x5c) { - return `'\\${ch}'`; - } else if (value >= 0x20 && value <= 0x7e) { - return `'${ch}'`; - } else { - return `0x${value.toString(16)}`; - } - } -} diff --git a/src/implementation/c/constants.ts b/src/implementation/c/constants.ts deleted file mode 100644 index b4e6ac3..0000000 --- a/src/implementation/c/constants.ts +++ /dev/null @@ -1,44 +0,0 @@ -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 = 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 = new Map(); -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 = 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 = 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/src/implementation/c/helpers/match-sequence.ts b/src/implementation/c/helpers/match-sequence.ts deleted file mode 100644 index 0b04176..0000000 --- a/src/implementation/c/helpers/match-sequence.ts +++ /dev/null @@ -1,73 +0,0 @@ -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; - -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/src/implementation/c/index.ts b/src/implementation/c/index.ts deleted file mode 100644 index 575e94a..0000000 --- a/src/implementation/c/index.ts +++ /dev/null @@ -1,209 +0,0 @@ -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, { 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 '); - out.push('#include '); - out.push('#include '); - 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 '); - out.push(' #else /* !_MSC_VER */'); - out.push(' #include '); - out.push(' #endif /* _MSC_VER */'); - out.push('#endif /* __SSE4_2__ */'); - out.push(''); - - out.push('#ifdef __ARM_NEON__'); - out.push(' #include '); - out.push('#endif /* __ARM_NEON__ */'); - out.push(''); - - out.push('#ifdef __wasm__'); - out.push(' #include '); - out.push('#endif /* __wasm__ */'); - out.push(''); - - out.push('#ifdef _MSC_VER'); - out.push(' #define ALIGN(n) _declspec(align(n))'); - out.push(' #define UNREACHABLE __assume(0)'); - out.push('#else /* !_MSC_VER */'); - out.push(' #define ALIGN(n) __attribute__((aligned(n)))'); - out.push(' #define UNREACHABLE __builtin_unreachable()'); - 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; - const rootState = root.get>(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(' }'); - - 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/src/implementation/c/node/base.ts b/src/implementation/c/node/base.ts deleted file mode 100644 index 51f90bb..0000000 --- a/src/implementation/c/node/base.ts +++ /dev/null @@ -1,77 +0,0 @@ -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; - readonly noAdvance: boolean; - readonly value?: number; -} - -export abstract class 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/src/implementation/c/node/consume.ts b/src/implementation/c/node/consume.ts deleted file mode 100644 index dbc6342..0000000 --- a/src/implementation/c/node/consume.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Node } from './base'; - -export class Consume extends Node { - 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/src/implementation/c/node/empty.ts b/src/implementation/c/node/empty.ts deleted file mode 100644 index e412a61..0000000 --- a/src/implementation/c/node/empty.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Node } from './base'; - -export class Empty extends Node { - public doBuild(out: string[]): void { - const otherwise = this.ref.otherwise!; - - if (!otherwise.noAdvance) { - this.prologue(out); - } - - this.tailTo(out, otherwise); - } -} diff --git a/src/implementation/c/node/error.ts b/src/implementation/c/node/error.ts deleted file mode 100644 index f55e888..0000000 --- a/src/implementation/c/node/error.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { STATE_ERROR } from '../constants'; -import { Node } from './base'; - -class ErrorNode extends Node { - 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/src/implementation/c/node/index.ts b/src/implementation/c/node/index.ts deleted file mode 100644 index d2a0b6b..0000000 --- a/src/implementation/c/node/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -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'; - -class Error extends ErrorNode {} - -export default { - Consume, - Empty, - Error, - Invoke, - Pause, - Sequence, - Single, - SpanEnd, - SpanStart, - TableLookup, -}; diff --git a/src/implementation/c/node/invoke.ts b/src/implementation/c/node/invoke.ts deleted file mode 100644 index 8247f63..0000000 --- a/src/implementation/c/node/invoke.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Node } from './base'; - -export class Invoke extends Node { - 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/src/implementation/c/node/pause.ts b/src/implementation/c/node/pause.ts deleted file mode 100644 index 623c909..0000000 --- a/src/implementation/c/node/pause.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { STATE_ERROR } from '../constants'; -import { Error as ErrorNode } from './error'; - -export class Pause extends ErrorNode { - 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/src/implementation/c/node/sequence.ts b/src/implementation/c/node/sequence.ts deleted file mode 100644 index 8b04aed..0000000 --- a/src/implementation/c/node/sequence.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { - SEQUENCE_COMPLETE, SEQUENCE_MISMATCH, SEQUENCE_PAUSE, -} from '../constants'; -import { Node } from './base'; - -export class Sequence extends Node { - 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/src/implementation/c/node/single.ts b/src/implementation/c/node/single.ts deleted file mode 100644 index b80a4ea..0000000 --- a/src/implementation/c/node/single.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Node } from './base'; - -export class Single extends Node { - 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/src/implementation/c/node/span-end.ts b/src/implementation/c/node/span-end.ts deleted file mode 100644 index ebec25e..0000000 --- a/src/implementation/c/node/span-end.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { STATE_ERROR } from '../constants'; -import { Node } from './base'; - -export class SpanEnd extends Node { - 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/src/implementation/c/node/span-start.ts b/src/implementation/c/node/span-start.ts deleted file mode 100644 index 0b5cd12..0000000 --- a/src/implementation/c/node/span-start.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Node } from './base'; - -export class SpanStart extends Node { - 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/src/implementation/c/node/table-lookup.ts b/src/implementation/c/node/table-lookup.ts deleted file mode 100644 index b41b143..0000000 --- a/src/implementation/c/node/table-lookup.ts +++ /dev/null @@ -1,381 +0,0 @@ -import * as assert from 'assert'; -import * as frontend from 'llparse-frontend'; - -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 MAX_NEON_RANGES = 6; -const MAX_WASM_RANGES = 6; -const SSE_ALIGNMENT = 16; - -interface ITable { - readonly name: string; - readonly declaration: ReadonlyArray; -} - -export class TableLookup extends Node { - 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. - if (this.canVectorize()) { - this.buildSSE(out); - this.buildNeon(out); - this.buildWASM(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[] = []; - 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 canVectorize(): boolean { - // 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 || - edge.node.ref !== this.ref - ) { - return false; - } - - assert.strictEqual(edge.noAdvance, false); - - return true; - } - - private buildRanges(edge: frontend.node.TableLookup["edges"][0]): number[] { - // NOTE: keys are sorted - const 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); - } - return ranges; - } - - private buildSSE(out: string[]): boolean { - const ctx = this.compilation; - - const edge = this.ref.edges[0]; - assert(edge !== undefined); - - const ranges = this.buildRanges(edge); - - 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 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[] = []; - 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 buildNeon(out: string[]): boolean { - const ctx = this.compilation; - - const edge = this.ref.edges[0]; - assert(edge !== undefined); - - const ranges = this.buildRanges(edge); - - if (ranges.length === 0) { - return false; - } - - // Way too many calls would be required - if (ranges.length > MAX_NEON_RANGES) { - return false; - } - - out.push('#ifdef __ARM_NEON__'); - out.push(`while (${ctx.endPosArg()} - ${ctx.posArg()} >= 16) {`); - out.push(' uint8x16_t input;'); - out.push(' uint8x16_t single;'); - out.push(' uint8x16_t mask;'); - out.push(' uint8x8_t narrow;'); - out.push(' uint64_t match_mask;'); - out.push(' int match_len;'); - out.push(''); - out.push(' /* Load input */'); - out.push(` input = vld1q_u8(${ctx.posArg()});`); - - out.push(' /* Find first character that does not match `ranges` */'); - function v128(value: number): string { - return `vdupq_n_u8(${ctx.toChar(value)})`; - } - - for (let off = 0; off < ranges.length; off += 2) { - const start = ranges[off]; - const end = ranges[off + 1]; - assert(start !== undefined); - assert(end !== undefined); - - // Same character, equality is sufficient (and faster) - if (start === end) { - out.push(` single = vceqq_u8(input, ${v128(start)});`); - } else { - out.push(` single = vandq_u16(`); - out.push(` vcgeq_u8(input, ${v128(start)}),`); - out.push(` vcleq_u8(input, ${v128(end)})`); - out.push(' );'); - } - - if (off === 0) { - out.push(' mask = single;'); - } else { - out.push(' mask = vorrq_u16(mask, single);'); - } - } - - // https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon - out.push(' narrow = vshrn_n_u16(mask, 4);'); - out.push(' match_mask = ~vget_lane_u64(vreinterpret_u64_u8(narrow), 0);'); - out.push(' match_len = __builtin_ctzll(match_mask) >> 2;'); - out.push(' if (match_len != 16) {'); - out.push(` ${ctx.posArg()} += match_len;`); - { - const tmp: string[] = []; - this.tailTo(tmp, this.ref.otherwise!); - ctx.indent(out, tmp, ' '); - } - out.push(' }'); - out.push(` ${ctx.posArg()} += 16;`); - out.push('}'); - - out.push(`if (${ctx.posArg()} == ${ctx.endPosArg()}) {`); - { - const tmp: string[] = []; - this.pause(tmp); - this.compilation.indent(out, tmp, ' '); - } - out.push('}'); - - out.push('#endif /* __ARM_NEON__ */'); - - return true; - } - - private buildWASM(out: string[]): boolean { - const ctx = this.compilation; - - const edge = this.ref.edges[0]; - assert(edge !== undefined); - - const ranges = this.buildRanges(edge); - - if (ranges.length === 0) { - return false; - } - - // Way too many calls would be required - if (ranges.length > MAX_WASM_RANGES) { - return false; - } - - out.push('#ifdef __wasm_simd128__'); - out.push(`while (${ctx.endPosArg()} - ${ctx.posArg()} >= 16) {`); - out.push(' v128_t input;'); - out.push(' v128_t mask;'); - out.push(' v128_t single;'); - out.push(' int match_len;'); - out.push(''); - out.push(' /* Load input */'); - out.push(` input = wasm_v128_load(${ctx.posArg()});`); - - out.push(' /* Find first character that does not match `ranges` */'); - function v128(value: number): string { - return `wasm_u8x16_const_splat(${ctx.toChar(value)})`; - } - - for (let off = 0; off < ranges.length; off += 2) { - const start = ranges[off]; - const end = ranges[off + 1]; - assert(start !== undefined); - assert(end !== undefined); - - // Same character, equality is sufficient (and faster) - if (start === end) { - out.push(` single = wasm_i8x16_eq(input, ${v128(start)});`); - } else { - out.push(` single = wasm_v128_and(`); - out.push(` wasm_i8x16_ge(input, ${v128(start)}),`); - out.push(` wasm_i8x16_le(input, ${v128(end)})`); - out.push(' );'); - } - - if (off === 0) { - out.push(' mask = single;'); - } else { - out.push(' mask = wasm_v128_or(mask, single);'); - } - } - out.push(' match_len = __builtin_ctz('); - out.push(' ~wasm_i8x16_bitmask(mask)'); - out.push(' );'); - out.push(' if (match_len != 16) {'); - out.push(` ${ctx.posArg()} += match_len;`); - { - const tmp: string[] = []; - this.tailTo(tmp, this.ref.otherwise!); - ctx.indent(out, tmp, ' '); - } - out.push(' }'); - out.push(` ${ctx.posArg()} += 16;`); - out.push('}'); - - out.push(`if (${ctx.posArg()} == ${ctx.endPosArg()}) {`); - { - const tmp: string[] = []; - this.pause(tmp); - this.compilation.indent(out, tmp, ' '); - } - out.push('}'); - - out.push('#endif /* __wasm_simd128__ */'); - - 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/src/implementation/c/transform/base.ts b/src/implementation/c/transform/base.ts deleted file mode 100644 index 82028d5..0000000 --- a/src/implementation/c/transform/base.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; - -export abstract class Transform { - constructor(public readonly ref: T) { - } - - public abstract build(ctx: Compilation, value: string): string; -} diff --git a/src/implementation/c/transform/id.ts b/src/implementation/c/transform/id.ts deleted file mode 100644 index 6c6105f..0000000 --- a/src/implementation/c/transform/id.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Transform } from './base'; - -export class ID extends Transform { - public build(ctx: Compilation, value: string): string { - // Identity transformation - return value; - } -} diff --git a/src/implementation/c/transform/index.ts b/src/implementation/c/transform/index.ts deleted file mode 100644 index c13ba50..0000000 --- a/src/implementation/c/transform/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -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/src/implementation/c/transform/to-lower-unsafe.ts b/src/implementation/c/transform/to-lower-unsafe.ts deleted file mode 100644 index 27f608c..0000000 --- a/src/implementation/c/transform/to-lower-unsafe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Transform } from './base'; - -export class ToLowerUnsafe extends Transform { - public build(ctx: Compilation, value: string): string { - return `((${value}) | 0x20)`; - } -} diff --git a/src/implementation/c/transform/to-lower.ts b/src/implementation/c/transform/to-lower.ts deleted file mode 100644 index f639ef1..0000000 --- a/src/implementation/c/transform/to-lower.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as frontend from 'llparse-frontend'; - -import { Compilation } from '../compilation'; -import { Transform } from './base'; - -export class ToLower extends Transform { - public build(ctx: Compilation, value: string): string { - return `((${value}) >= 'A' && (${value}) <= 'Z' ? ` + - `(${value} | 0x20) : (${value}))`; - } -} diff --git a/src/llparse.ts b/src/llparse.ts new file mode 100644 index 0000000..53b70c1 --- /dev/null +++ b/src/llparse.ts @@ -0,0 +1,91 @@ +import * as assert from 'assert'; +import { builder } from 'bitcode'; + +import { TransformAPI, CodeAPI } from './api'; +import * as internal from './llparse/'; + +export interface ILLParseOptions { + debug?: boolean; +} + +export class LLParse { + public readonly code = new CodeAPI(); + public readonly transform = new TransformAPI(); + private readonly properties: { + set: Set, + list: internal.compiler.ICompilerStateProperty[], + }; + + public static create(public readonly prefix: string): LLparse { + return new LLParse(prefix); + } + + constructor(private readonly prefix: string = 'llparse') { + this.properties = { + set: new Set(), + list: [] + }; + } + + public node(name: string): internal.node.Match { + return new internal.node.Match(name); + } + + public error(code: number, reason: string): internal.node.Error { + return new internal.node.Error(code, reason); + } + + public invoke(code: internal.code.Code, + map: { [key: number]: Node } | Node | undefined, + otherwise?: Node): internal.node.Invoke { + if (map === undefined) { + return new internal.node.Invoke(code, {}); + } else if (map instanceof Node) { + return new internal.node.Invoke(code, {}, map); + } else { + return new internal.node.Invoke(code, map, otherwise); + } + } + + public span(callback: internal.Code): internal.node.Span { + return new internal.Span(callback); + } + + public consume(field: string): internal.node.Consume { + return new internal.node.Consume(field); + } + + public pause(code: number, reason: string): internal.node.Pause { + return new internal.node.Pause(code, reason); + } + + public property(ty: string, name: string): void { + if (/^_/.test(name)) { + throw new Error(`Can't use internal property name: "${name}"`); + } + + if (this.properties.set.has(name)) { + throw new Error(`Duplicate property with a name: "${name}"`); + } + + if (!internal.constants.USER_TYPES.has(ty)) { + throw new Error(`Unknown property type: "${ty}"`); + } + const bitcodeTy = internal.constants.USER_TYPES.get(ty)!; + + props.set.add(name); + props.list.push({ ty: bitcodeTy, name }); + } + + public build(root: internal.node.Node, options?: ILLParseOptions) + : internal.compiler.ICompilerBuildResult { + options = options || {}; + + const c = new internal.compiler.Compiler({ + prefix: this.prefix, + properties: this.properties.list, + debug: options.debug === undefined ? false : options.debug + }); + return c.build(root); + } +} diff --git a/src/llparse/cases/base.ts b/src/llparse/cases/base.ts new file mode 100644 index 0000000..7cb37ce --- /dev/null +++ b/src/llparse/cases/base.ts @@ -0,0 +1,16 @@ +import * as node from '../node'; +import { Buffer } from 'buffer'; + +export interface ICaseLinearizeResult { + key: Buffer; + next: node.Node; + value: number | undefined; + noAdvance: boolean; +} + +export abstract class Case { + constructor(public readonly type: string, public readonly next: node.Node) { + } + + public abstract linearize(): ICaseLinearizeResult[]; +} diff --git a/src/llparse/cases/index.ts b/src/llparse/cases/index.ts new file mode 100644 index 0000000..07c4ee4 --- /dev/null +++ b/src/llparse/cases/index.ts @@ -0,0 +1,5 @@ +export { ICaseLinearizeResult, Case } from './base'; +export { Match } from './match'; +export { Peek } from './peek'; +export { Select } from './select'; +export { Otherwise } from './otherwise'; diff --git a/src/llparse/cases/match.ts b/src/llparse/cases/match.ts new file mode 100644 index 0000000..1829a8f --- /dev/null +++ b/src/llparse/cases/match.ts @@ -0,0 +1,27 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import * as node from '../node'; +import * as utils from '../utils'; +import { ICaseLinearizeResult, Case } from './case'; + +export class Match extends Case { + public readonly key: Buffer; + + constructor(key: string, next: node.Node) { + super('match', next); + + this.key = utils.toBuffer(key); + assert(this.key.length > 0, + '`.match()` must get at least 1 byte as a first argument'); + } + + public linearize(): ICaseLinearizeResult[] { + return [ { + key: this.key, + next: this.next, + value: undefined, + noAdvance: false + } ]; + } +} diff --git a/src/llparse/cases/otherwise.ts b/src/llparse/cases/otherwise.ts new file mode 100644 index 0000000..b42a131 --- /dev/null +++ b/src/llparse/cases/otherwise.ts @@ -0,0 +1,12 @@ +import * as node from '../node'; +import { ICaseLinearizeResult, Case } from './case'; + +export class Otherwise extends Case { + constructor(next: node.Node, public readonly skip: boolean = false) { + super('otherwise', next); + } + + public linearize(): ICaseLinearizeResult[] { + throw new Error('Should not be called'); + } +} diff --git a/src/llparse/cases/peek.ts b/src/llparse/cases/peek.ts new file mode 100644 index 0000000..1b529e0 --- /dev/null +++ b/src/llparse/cases/peek.ts @@ -0,0 +1,27 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import * as node from '../node'; +import * as utils from '../utils'; +import { Case, ICaseLinearizeResult } from './case'; + +export class Peek extends Case { + public readonly key: Buffer; + + constructor(key: string, next: node.Node) { + super('peek', next); + + this.key = utils.toBuffer(key); + assert.strictEqual(this.key.length, 1, + '`.peek()` must get exactly 1 byte as a first argument'); + } + + public linearize(): ICaseLinearizeResult[] { + return [ { + key: this.key, + next: this.next, + value: undefined, + noAdvance: true + } ]; + } +} diff --git a/src/llparse/cases/select.ts b/src/llparse/cases/select.ts new file mode 100644 index 0000000..a868bf9 --- /dev/null +++ b/src/llparse/cases/select.ts @@ -0,0 +1,39 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import * as node from '../node'; +import * as utils from '../utils'; +import { ICaseLinearizeResult, Case } from './case'; + +export class Select extends Case { + private readonly privMap: Map = new Map(); + + constructor(next) { + super('select', next); + } + + public get map(): ReadonlyMap { + return this.privMap; + } + + public add(key, value): void { + this.privMap.set(key, value); + } + + public linearize(): ICaseLinearizeResult[] { + const res: ICaseLinearizeResult = []; + + this.map.forEach((value, key) => { + const buffer: Buffer = utils.toBuffer(key); + assert(buffer.length > 0, 'Select must have non-empty argument'); + + res.push({ + key: buffer, + next: this.next, + value, + noAdvance: false + }); + }); + return res; + } +} diff --git a/src/llparse/code/base.ts b/src/llparse/code/base.ts new file mode 100644 index 0000000..428b805 --- /dev/null +++ b/src/llparse/code/base.ts @@ -0,0 +1,7 @@ +export type Signature = 'match' | 'value'; + +export abstract class Code { + constructor(public readonly signature: Signature, + public readonly name: string) { + } +} diff --git a/src/llparse/code/field-value.ts b/src/llparse/code/field-value.ts new file mode 100644 index 0000000..0c0db1d --- /dev/null +++ b/src/llparse/code/field-value.ts @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import { Signature } from './code'; +import { Field } from './field'; + +export class FieldValue extends Field { + constructor(signature: Signature, name: string, field: string, + public readonly value: number) { + super(signature, name, field); + + assert.strictEqual(value, value | 0, + '`value` argument of FieldValue must be an integer'); + } +} diff --git a/src/llparse/code/field.ts b/src/llparse/code/field.ts new file mode 100644 index 0000000..9f932dd --- /dev/null +++ b/src/llparse/code/field.ts @@ -0,0 +1,8 @@ +import { Code, Signature } from './code'; + +export class Field extends Code { + constructor(signature: Signature, name: string, + public readonly field: string) { + super(signature, name + '_' + field); + } +} diff --git a/src/llparse/code/index.ts b/src/llparse/code/index.ts new file mode 100644 index 0000000..2a7f452 --- /dev/null +++ b/src/llparse/code/index.ts @@ -0,0 +1,17 @@ +export { Code, Signature } from './base'; +export { Match } from './match'; +export { Value } from './value'; +export { Span } from './span'; + +// Helpers + +export { Field } from './field'; +export { FieldValue } from './field-value'; + +export { Store } from './store'; +export { Load } from './load'; +export { IMulAddOptions, MulAdd } from './mul-add'; +export { Update } from './update'; +export { IsEqual } from './is-equal'; +export { Or } from './or'; +export { Test } from './test'; diff --git a/src/llparse/code/is-equal.ts b/src/llparse/code/is-equal.ts new file mode 100644 index 0000000..91bb957 --- /dev/null +++ b/src/llparse/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/src/llparse/code/load.ts b/src/llparse/code/load.ts new file mode 100644 index 0000000..9f3df2e --- /dev/null +++ b/src/llparse/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/src/llparse/code/match.ts b/src/llparse/code/match.ts new file mode 100644 index 0000000..4f592f1 --- /dev/null +++ b/src/llparse/code/match.ts @@ -0,0 +1,7 @@ +export { Code } from './code'; + +export class Match extends Code { + constructor(name: string) { + super('match', name); + } +} diff --git a/src/llparse/code/mul-add.ts b/src/llparse/code/mul-add.ts new file mode 100644 index 0000000..cb7bfaf --- /dev/null +++ b/src/llparse/code/mul-add.ts @@ -0,0 +1,40 @@ +import * as assert from 'assert'; + +import { Field } from './field'; + +export interface IMulAddOptions { + base: number; + max?: number; + signed?: boolean; +} + +export interface IMulAddCompleteOptions { + base: number; + max: number; + signed: boolean; +} + +export class MulAdd extends Field { + public readonly options: IMulAddCompleteOptions; + + constructor(field: string, options: IMulAddOptions) { + super('value', 'mul_add', field); + + const complete: IMulAddCompleteOptions = { + base: options.base, + max: options.max === undefined ? 0 : options.max, + signed: options.signed === undefined ? true : options.signed, + }; + + assert(complete.max >= 0, + '`MulAdd.options.max` must be a non-negative number'); + assert(complete.base > 0, + '`MulAdd.options.base` must be a positive number'); + assert.strictEqual(complete.max, complete.max | 0, + '`MulAdd.options.max` must be an integer'); + assert.strictEqual(complete.base, complete.base | 0, + '`MulAdd.options.max` must be an integer'); + + this.options = complete; + } +} diff --git a/src/llparse/code/or.ts b/src/llparse/code/or.ts new file mode 100644 index 0000000..0525a6a --- /dev/null +++ b/src/llparse/code/or.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class Or extends code.FieldValue { + constructor(field: string, value: number) { + super('match', 'or', field, value); + } +} diff --git a/src/llparse/code/span.ts b/src/llparse/code/span.ts new file mode 100644 index 0000000..b97e09e --- /dev/null +++ b/src/llparse/code/span.ts @@ -0,0 +1,5 @@ +import { Match } from './match'; + +export class Span extends Match { + // no-op +} diff --git a/src/llparse/code/store.ts b/src/llparse/code/store.ts new file mode 100644 index 0000000..84abfef --- /dev/null +++ b/src/llparse/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/src/llparse/code/test.ts b/src/llparse/code/test.ts new file mode 100644 index 0000000..3a7ac26 --- /dev/null +++ b/src/llparse/code/test.ts @@ -0,0 +1,7 @@ +import { FieldValue } from './field-value'; + +export class Test extends FieldValue { + constructor(field: string, mask: number) { + super('match', 'test', field, mask); + } +} diff --git a/src/llparse/code/update.ts b/src/llparse/code/update.ts new file mode 100644 index 0000000..de62476 --- /dev/null +++ b/src/llparse/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/src/llparse/code/value.ts b/src/llparse/code/value.ts new file mode 100644 index 0000000..8000da9 --- /dev/null +++ b/src/llparse/code/value.ts @@ -0,0 +1,7 @@ +import { Code } from './code'; + +export class Value extends Code { + constructor(name: string) { + super('value', name); + } +} diff --git a/src/llparse/compiler/code/base.ts b/src/llparse/compiler/code/base.ts new file mode 100644 index 0000000..f18a469 --- /dev/null +++ b/src/llparse/compiler/code/base.ts @@ -0,0 +1,41 @@ +import * as assert from 'assert'; + +import { Signature } from '../../code'; +import { Compilation, types, values } from '../compilation'; + +import Func = values.constants.Func; + +// Just as a convenience +export { Func }; + +export abstract class Code { + protected privIsExternal: boolean = false; + protected privCacheKey: any = this; + + constructor(public readonly kind: string, + public readonly signature: Signature, + public readonly name: string) { + } + + public get isExternal(): boolean { return this.privIsExternal; } + public get cacheKey(): any { return this.privCacheKey; } + + public abstract build(): void; + + // Just for cache key generation + protected numKey(num): string { + if (num < 0) + return 'm' + (-num); + else + return num.toString(); + } + + protected getTypes(ctx: Compilation, fn: Func, field: string) { + const fieldType = ctx.state.lookupField(field).ty; + const returnType = fn.ty.toSignature().returnType; + assert(fieldType.isInt(), `"${field}" field is not of integer type`); + assert(returnType.isInt()); + + return { fieldType, returnType }; + } +} diff --git a/src/llparse/compiler/code/index.ts b/src/llparse/compiler/code/index.ts new file mode 100644 index 0000000..4824c64 --- /dev/null +++ b/src/llparse/compiler/code/index.ts @@ -0,0 +1,17 @@ +export { Code } from './base'; + +// External + +export { Match } from './match'; +export { Value } from './value'; +export { Span } from './span'; + +// Internal + +export { IsEqual } from './is-equal'; +export { Load } from './load'; +export { MulAdd } from './mul-add'; +export { Or } from './or'; +export { Store } from './store'; +export { Test } from './test'; +export { Update } from './update'; diff --git a/src/llparse/compiler/code/is-equal.ts b/src/llparse/compiler/code/is-equal.ts new file mode 100644 index 0000000..78da7a6 --- /dev/null +++ b/src/llparse/compiler/code/is-equal.ts @@ -0,0 +1,23 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +class IsEqual extends Code { + constructor(name: string, private readonly field: string, + private readonly value: number) { + super('is-equal', 'match', name); + + this.privCacheKey = `is_equal_${this.field}_${this.numKey(this.value)}`; + } + + public build(ctx: Compilation, fn: Func): void { + const body = fn.body; + const field = this.field; + const value = this.value; + + const { fieldType, returnType } = this.getTypes(ctx, fn, field); + const fieldValue = ctx.load(fn, body, field); + + const cmp = body.icmp('eq', fieldValue, fieldType.val(value)); + body.ret(ctx.truncate(body, cmp, returnType)); + } +} diff --git a/src/llparse/compiler/code/load.ts b/src/llparse/compiler/code/load.ts new file mode 100644 index 0000000..dbf3570 --- /dev/null +++ b/src/llparse/compiler/code/load.ts @@ -0,0 +1,20 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +export class Load extends Code { + constructor(name: string, private readonly field: string) { + super('load', 'match', name); + + this.privCacheKey = `load_${this.field}`; + } + + public build(ctx: Compilation, fn: Func): void { + const body = fn.body; + const field = this.field; + + const { returnType } = this.getTypes(ctx, fn, field); + + const adj = ctx.truncate(body, ctx.load(fn, body, field), returnType); + body.ret(adj); + } +} diff --git a/src/llparse/compiler/code/match.ts b/src/llparse/compiler/code/match.ts new file mode 100644 index 0000000..80a699c --- /dev/null +++ b/src/llparse/compiler/code/match.ts @@ -0,0 +1,15 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +class Match extends Code { + constructor(name: string) { + super('match', 'match', name); + + this.privIsExternal = true; + this.privCacheKey = 'external_' + name; + } + + public build(ctx: Compilation, fn: Func): void { + throw new Error('External code can\'t be built'); + } +} diff --git a/src/llparse/compiler/code/mul-add.ts b/src/llparse/compiler/code/mul-add.ts new file mode 100644 index 0000000..427c61b --- /dev/null +++ b/src/llparse/compiler/code/mul-add.ts @@ -0,0 +1,93 @@ +import { IMulAddOptions } from '../../code'; +import { BOOL } from '../../constants'; +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +export class MulAdd extends Code { + constructor(name: string, private readonly field: string, + private readonly options: IMulAddOptions) { + super('mulAdd', 'value', name); + + this.privCacheKey = `mul_add_${this.field}_${JSON.stringify(this.options)}`; + } + + public build(ctx: Compilation, fn: Func): void { + const ir = ctx.ir; + const body = fn.body; + const match = ctx.matchArg(fn); + const field = this.field; + const options = this.options; + + const { fieldType, returnType } = this.getTypes(ctx, fn, field); + + // Declare intrinsic functions + const overRet = ir.struct(); + overRet.addField(fieldType, 'result'); + overRet.addField(BOOL, 'overflow'); + overRet.finalize(); + + const overSig = ir.signature(overRet, [ fieldType, fieldType ]); + const postfix = `with.overflow.${fieldType.typeString}`; + const mulFn = ctx.declareFunction( + overSig, + `llvm.${options.signed ? 'smul': 'umul'}.${postfix}` + ); + const addFn = ctx.declareFunction( + overSig, + `llvm.${options.signed ? 'sadd': 'uadd'}.${postfix}` + ); + + // Truncate match and load field + const value = ctx.truncate(body, match, fieldType, + options.signed); + const fieldValue = ctx.load(fn, body, field); + + // Multiply + const mul = body.call(mulFn, [ fieldValue, fieldType.val(options.base) ]); + + const product = body.extractvalue(mul, overRet.lookupField('result').index); + const overflowBit = body.extractvalue(mul, + overRet.lookupField('overflow').index); + + const { left: isOverflow, right: normal } = + ctx.branch(body, overflowBit, [ 'unlikely', 'likely' ]); + isOverflow.name = 'overflow'; + isOverflow.ret(returnType.val(1)); + + normal.name = 'no_overflow'; + + // Add + const add = normal.call(addFn, [ product, value ]); + + const result = normal.extractvalue(add, + overRet.lookupField('result').index); + const addOverflowBit = normal.extractvalue(add, + overRet.lookupField('overflow').index); + + const { left: isAddOverflow, right: check } = + ctx.branch(normal, addOverflowBit, [ 'unlikely', 'likely' ]); + isAddOverflow.name = 'add_overflow'; + check.name = 'check'; + + isAddOverflow.ret(returnType.val(1)); + + // Check that we're within the limits + let store; + if (options.max) { + const cond = options.signed ? 'sgt' : 'ugt'; + const cmp = check.icmp(cond, result, fieldType.val(options.max)); + const branch = ctx.branch(check, cmp, [ 'unlikely', 'likely' ]); + branch.left.name = 'max_overflow'; + + branch.left.ret(returnType.val(1)); + + store = branch.right; + } else { + store = check; + } + store.name = 'store'; + + ctx.store(fn, store, field, result); + store.ret(returnType.val(0)); + } +} diff --git a/src/llparse/compiler/code/or.ts b/src/llparse/compiler/code/or.ts new file mode 100644 index 0000000..1d70676 --- /dev/null +++ b/src/llparse/compiler/code/or.ts @@ -0,0 +1,23 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +export class Or extends Code { + constructor(name, private readonly field: string, + private readonly value: number) { + super('or', 'match', name); + this.privCacheKey = `or_${this.field}_${this.numKey(this.value)}`; + } + + public build(ctx: Compilation, fn: Func): void { + const body = fn.body; + const field = this.field; + const value = this.value; + + const { fieldType, returnType } = this.getTypes(ctx, fn, field); + + const current = ctx.load(fn, body, field); + const result = body.binop('or', current, fieldType.val(value)); + ctx.store(fn, body, field, result); + body.ret(returnType.val(0)); + } +} diff --git a/src/llparse/compiler/code/span.ts b/src/llparse/compiler/code/span.ts new file mode 100644 index 0000000..67143df --- /dev/null +++ b/src/llparse/compiler/code/span.ts @@ -0,0 +1,15 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +export class Span extends Code { + constructor(name: string) { + super('span', 'match', name); + + this.privIsExternal = true; + this.privCacheKey = 'external_' + name; + } + + public build(ctx: Compilation, fn: Func): void { + throw new Error('External code can\'t be built'); + } +} diff --git a/src/llparse/compiler/code/store.ts b/src/llparse/compiler/code/store.ts new file mode 100644 index 0000000..cf1d438 --- /dev/null +++ b/src/llparse/compiler/code/store.ts @@ -0,0 +1,24 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +export class Store extends Code { + constructor(name: string, private readonly field: string) { + super('store', 'value', name); + + this.privCacheKey = `store_${this.field}`; + } + + public build(ctx: Compilation, fn: Func): void { + const body = fn.body; + const field = this.field; + + const match = ctx.matchArg(fn); + + const { fieldType, returnType } = this.getTypes(ctx, fn, field); + + const adj = ctx.truncate(body, match, fieldType); + + ctx.store(fn, body, field, adj); + body.ret(returnType.val(0)); + } +} diff --git a/src/llparse/compiler/code/test.ts b/src/llparse/compiler/code/test.ts new file mode 100644 index 0000000..efa5729 --- /dev/null +++ b/src/llparse/compiler/code/test.ts @@ -0,0 +1,26 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +class Test extends Code { + constructor(name: string, private readonly field: string, + private readonly value: string) { + super('test', 'match', name); + + this.privCacheKey = `test_${this.field}_${this.numKey(this.value)}`; + } + + build(ctx: Compilation, fn: Func): void { + const body = fn.body; + const field = this.field; + const value = this.value; + + const { fieldType, returnType } = this.getTypes(ctx, fn, field); + + const current = ctx.load(fn, body, field); + + const masked = body.binop('and', current, fieldType.val(value)); + const bool = body.icmp('eq', masked, fieldType.val(value)); + + body.ret(ctx.truncate(body, bool, returnType)); + } +} diff --git a/src/llparse/compiler/code/update.ts b/src/llparse/compiler/code/update.ts new file mode 100644 index 0000000..be18958 --- /dev/null +++ b/src/llparse/compiler/code/update.ts @@ -0,0 +1,22 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +class Update extends Code { + constructor(name: string, private readonly field: string, + private readonly value: number) { + super('update', 'match', name); + + this.privCacheKey = `update_${this.field}_${this.numKey(this.value)}`; + } + + public build(ctx: Compilation, fn: Func): void { + const body = fn.body; + const field = this.field; + const value = this.value; + + const { fieldType, returnType } = this.getTypes(ctx, fn, field); + + ctx.store(fn, body, field, fieldType.val(value)); + body.ret(returnType.val(0)); + } +} diff --git a/src/llparse/compiler/code/value.ts b/src/llparse/compiler/code/value.ts new file mode 100644 index 0000000..b86d167 --- /dev/null +++ b/src/llparse/compiler/code/value.ts @@ -0,0 +1,15 @@ +import { Compilation } from '../compilation'; +import { Code, Func } from './base'; + +class Value extends Code { + constructor(name: string) { + super('value', 'value', name); + + this.privIsExternal = true; + this.privCacheKey = 'external_' + name; + } + + public build(ctx: Compilation, fn: Func): void { + throw new Error('External code can\'t be built'); + } +} diff --git a/src/llparse/compiler/compilation.ts b/src/llparse/compiler/compilation.ts new file mode 100644 index 0000000..c2d34b2 --- /dev/null +++ b/src/llparse/compiler/compilation.ts @@ -0,0 +1,512 @@ +import * as asert from 'assert'; +import * as bitcode from 'bitcode'; +import { Buffer } from 'buffer'; + +import { + CCONV, + + INT, TYPE_INPUT, TYPE_OUTPUT, TYPE_MATCH, TYPE_INDEX, TYPE_ERROR, TYPE_REASON, + TYPE_DATA, + + ATTR_STATE, ATTR_POS, ATTR_ENDPOS, + + ARG_STATE, ARG_POS, ARG_ENDPOS, ARG_MATCH, +} from '../constants'; +import { Transform } from './transform'; + +import * as code from '../code'; +import * as compilerCode from './code'; + +import builder = bitcode.builder; +import values = builder.values; +import Declaration = values.constants.Declaration; +import Func = values.constants.Func; +import BasicBlock = values.constants.Func; +import Value = values.Value; +import Type = builder.types.Type; + +export { values, Func, BasicBlock }; + +export interface INodeID { + name: string; + sourceName: string; +} + +export interface INodePosition { + current: Value; + next?: Value; +} + +export type Weight = 'likely' | 'unlikely' | number; + +export class Compilation { + constructor(options: any) { + this.options = Object.assign({}, options); + + this.bitcode = new bitcode.Module(); + this.ir = this.bitcode.createBuilder(); + this.prefix = this.options.prefix; + + this.root = this.options.root; + + this.state = this.ir.struct(`${this.prefix}_state`); + this.initializers = []; + + this.signature = { + node: this.ir.signature(TYPE_OUTPUT, [ + this.state.ptr(), + TYPE_INPUT, + TYPE_INPUT, + TYPE_MATCH + ]), + callback: { + match: this.ir.signature(INT, [ + this.state.ptr(), TYPE_INPUT, TYPE_INPUT + ]), + value: this.ir.signature(INT, [ + this.state.ptr(), TYPE_INPUT, TYPE_INPUT, TYPE_MATCH + ]), + span: null + } + }; + this.signature.callback.span = this.signature.callback.match; + + this.INVARIANT_GROUP = this.ir.metadata([ + this.ir.metadata('llparse.invariant') + ]); + + this.codeCache = new Map(); + this.cstringCache = new Map(); + this.debugMethod = null; + + this.namespace = new Set(); + + // Intermediate results from various build stages + this.stageResults = {}; + } + + public id(name, prefix = '', postfix = ''): INodeID { + let res = prefix + name + postfix; + if (this.namespace.has(res)) { + let i; + for (i = 1; i <= this.namespace.size; i++) + if (!this.namespace.has(res + '_' + i)) + break; + res += '_' + i; + } + + this.namespace.add(res); + return { name: res, sourceName: name }; + } + + public build(): void { + // Private fields + this.declareField(this.signature.node.ptr(), '_current', + (type, ctx) => ctx.stageResults['node-builder'].entry); + + this.declareField(TYPE_INDEX, '_index', type => type.val(0)); + + // Some stages may add more private fields + this.buildStages(this.options.stages.before); + + // Public fields + this.declareField(TYPE_ERROR, 'error', type => type.val(0)); + this.declareField(TYPE_REASON, 'reason', type => type.val(null)); + this.declareField(TYPE_INPUT, 'error_pos', type => type.val(null)); + this.declareField(TYPE_DATA, 'data', type => type.val(null)); + + // Custom fields + this.options.properties.forEach((prop) => { + this.declareField(prop.type, prop.name, (type) => { + if (type.isPointer()) + return type.val(null); + else + return type.val(0); + }); + }); + + // Lock up the struct + this.state.finalize(); + + // Some stages may add more private fields + this.buildStages(this.options.stages.after); + } + + public end(): Buffer { + return this.bitcode.build(this.ir); + } + + public buildCState(): string { + const out = []; + + out.push(`typedef struct ${this.prefix}_state_s ${this.prefix}_state_t;`); + out.push(`struct ${this.prefix}_state_s {`); + + this.state.fields.forEach((field) => { + let type = field.ty; + if (type.isPointer()) { + if (field.name === 'error_pos' || field.name === 'reason') + type = 'const char*'; + else + type = 'void*'; + } else { + assert(type.isInt(), 'Unsupported type: ' + type.type); + if (type.width === 8) + type = 'uint8_t'; + else if (type.width === 16) + type = 'uint16_t'; + else if (type.width === 32) + type = 'uint32_t'; + else if (type.width === 64) + type = 'uint64_t'; + else + throw new Error('Unsupported type width: ' + type.width); + } + + out.push(` ${type} ${field.name};`); + }); + + out.push('};'); + + return out.join('\n'); + } + + private buildStages(stages): void { + stages.forEach(Stage => this.buildStage(Stage)); + } + + private buildStage(Stage): void { + const stage = new Stage(this); + this.stageResults[stage.name] = stage.build(); + } + + public declareFunction(signature: builder.types.Signature, + name: string): Func { + const res = signature.declareFunction(name); + this.bitcode.add(res); + return res; + } + + public defineFunction(signature: builder.types.Signature, name: string, + paramNames: ReadonlyArray): Func { + const res = signature.defineFunction(name, paramNames); + this.bitcode.add(res); + return res; + } + + // TODO(indutny): find better place for it? + private translateCode(code: code.Code): compilerCode.Code { + // User callbacks + if (code instanceof code.Match) + return new compilerCode.Match(code.name); + else if (code instanceof code.Value) + return new compilerCode.Value(code.name); + else if (code instanceof code.Span) + return new compilerCode.Span(code.name); + + // Internal helpers + let name = code.name; + if (code.field) + name += '_' + code.field; + + const id = this.id(name, 'c_').name; + if (code instanceof code.IsEqual) + return new compilerCode.IsEqual(id, code.field, code.value); + else if (code instanceof code.Load) + return new compilerCode.Load(id, code.field); + else if (code instanceof code.MulAdd) + return new compilerCode.MulAdd(id, code.field, code.options); + else if (code instanceof code.Or) + return new compilerCode.Or(id, code.field, code.value); + else if (code instanceof code.Store) + return new compilerCode.Store(id, code.field); + else if (code instanceof code.Test) + return new compilerCode.Test(id, code.field, code.value); + else if (code instanceof code.Update) + return new compilerCode.Update(id, code.field, code.value); + else + throw new Error('Unexpected code type of: ' + code.name); + } + + public buildCode(code: compilerCode.Code): Declaration { + const native = this.translateCode(code); + + const signatures = this.signature.callback; + const signature = native.signature === 'match' ? + signatures.match : signatures.value; + + const cacheKey = native.cacheKey; + if (this.codeCache.has(cacheKey)) { + const cached = this.codeCache.get(cacheKey); + assert(cached.ty.isEqual(signature), + `Conflicting code entries for "${native.name}"`); + return cached; + } + + let fn; + if (native.isExternal) { + const external = this.declareFunction(signature, native.name); + + // NOTE: this has no effect due to machine-specific flags + // TODO(indutny): find a way to make it inline the function + external.attrs.add('alwaysinline'); + + fn = external; + } else { + fn = this.buildCodeWithBody(native, signature); + } + + this.codeCache.set(cacheKey, fn); + return fn; + } + + private buildCodeWithBody(code: compilerCode.Code, + signature: builder.types.Signature): Func { + const args = [ ARG_STATE, ARG_POS, ARG_ENDPOS ]; + + if (code.signature === 'value') + args.push(ARG_MATCH); + + const fn = this.defineFunction(signature, this.prefix + '_' + code.name, + args); + + fn.linkage = 'internal'; + fn.cconv = CCONV; + fn.attrs.add([ 'nounwind', 'norecurse', 'ssp', 'uwtable' ]); + + code.build(this, fn); + + return fn; + } + + public cstring(value: string): builder.values.Global { + if (this.cstringCache.has(value)) { + return this.cstringCache.get(value)!; + } + + const res = this.addGlobalConst('cstr', this.ir.cstring(value)); + this.cstringCache.set(value, res); + return res; + } + + public blob(data: Buffer): builder.values.Global { + return this.addGlobalConst('blob', this.ir.blob(data)); + } + + private addGlobalConst(name: string, value: Value): builder.values.Global { + const id = this.id(name, 'g_').name; + const glob = this.ir.global(value.ty.ptr(), id, value); + glob.linkage = 'internal'; + glob.markConstant(); + this.bitcode.add(glob); + return glob; + } + + public debug(fn: Func, body: BasicBlock, message: string): BasicBlock { + if (!this.options.debug) + return body; + + const str = this.cstring(message); + const cast = body.getelementptr(str, INT.val(0), INT.val(0)); + + // Lazily declare debug method + if (this.debugMethod === undefined) { + const sig = this.ir.signature(this.ir.void(), + [ this.state.ptr(), TYPE_INPUT, TYPE_INPUT, TYPE_INPUT ]); + + this.debugMethod = this.declareFunction(sig, this.options.debug); + } + + const args = [ + this.stateArg(fn), + this.posArg(fn), + this.endPosArg(fn), + cast + ]; + + body.call(this.debugMethod, args); + + return body; + } + + public fn(signature: builder.types.Signature, name: string): Func { + name = this.prefix + '__' + name; + + let fn; + if (signature === this.signature.node) { + fn = this.defineFunction(this.signature.node, name, + [ ARG_STATE, ARG_POS, ARG_ENDPOS, ARG_MATCH ]); + + // TODO(indutny): reassess `minsize`. Looks like it gives best performance + // results right now, though. + fn.attrs.add([ 'nounwind', 'minsize' ]); + + // These are ABI dependent, but should bring us close to C-function + // inlining on x86_64 through -flto + fn.attrs.add([ 'ssp', 'uwtable' ]); + + fn.paramAttrs[0].add(ATTR_STATE); + fn.paramAttrs[1].add(ATTR_POS); + fn.paramAttrs[2].add(ATTR_ENDPOS); + } else if (signature === this.signature.callback.match) { + fn = this.defineFunction(this.signature.callback.match, name, + [ ARG_STATE, ARG_POS, ARG_ENDPOS ]); + } else if (signature === this.signature.callback.value) { + fn = this.defineFunction(this.signature.callback.match, name, + [ ARG_STATE, ARG_POS, ARG_ENDPOS, ARG_MATCH ]); + } else { + throw new Error('Unknown signature: ' + signature); + } + + fn.linkage = 'internal'; + fn.cconv = CCONV; + + return fn; + } + + private toBranchWeight(value: Weight): number { + if (value === 'likely') + return 0x10000; + else if (value === 'unlikely') + return 0x1; + + assert.strictEqual(value, value | 0); + return value; + } + + // TODO(indutny): return type + public branch(body: BasicBlock, cmp: Value, weights: ReadonlyArray) { + const onTrue = body.parent.createBlock('true'); + const onFalse = body.parent.createBlock('false'); + const branch = body.branch(cmp, onTrue, onFalse); + + if (weights) { + assert(Array.isArray(weights)); + assert.strictEqual(weights.length, 2); + + weights = weights.map(w => this.toBranchWeight(w)); + const meta = this.ir.metadata([ + this.ir.metadata('branch_weights'), + this.ir.metadata(INT.val(weights[0])), + this.ir.metadata(INT.val(weights[1])) + ]); + branch.metadata.set('prof', meta); + } + + // TODO(indutny): rename `left`/`right` to `onTrue`/`onFalse` + return { + left: onTrue, + right: onFalse + }; + } + + // TODO(indutny): return type + buildSwitch(body: BasicBlock, what: Value, values: ReadonlyArray, + weights: ReadonlyArray) { + const cases = []; + const blocks = []; + values.forEach((value, i) => { + const block = body.parent.createBlock(`case_${i}`); + blocks.push(block); + cases.push({ + value: what.ty.val(value), + block, + }); + }); + + const otherwise = body.parent.createBlock('otherwise'); + const sw = body.switch(what, otherwise, cases); + + if (weights) { + assert(Array.isArray(weights)); + assert.strictEqual(weights.length, 1 + values.length); + + weights = weights.map((weight) => { + return this.ir.metadata(INT.val(this.toBranchWeight(weight))); + }); + + const meta = this.ir.metadata([ + this.ir.metadata('branch_weights') + ].concat(weights)); + sw.metadata.set('prof', meta); + } + + return { + otherwise, + cases: blocks + }; + } + + // TODO(indutny): return type + public buildTransform(transform: Transform, body: BasicBlock, + current: Value) { + if (transform.name === 'to_lower_unsafe') { + current = body.binop('or', current, TYPE_INPUT.to.val(0x20)); + } else { + throw new Error('Unsupported transform: ' + transform.name); + } + + return { body, current }; + } + + public truncate(body: BasicBlock, from: Value, toType: Type, + isSigned: boolean = false): Value { + const fromTy = from.ty; + assert(toType.isInt()); + assert(fromTy.isInt()); + + let res; + + // Same type! + if (fromTy.isEqual(toType)) { + return from; + // Extend + } else if (fromTy.width < toType.width) { + if (isSigned) + res = body.cast('sext', from, toType); + else + res = body.cast('zext', from, toType); + // Truncate + } else { + assert(fromTy.width > toType.width); + res = body.cast('trunc', from, toType); + } + + return res; + } + + public load(fn: Func, body: BasicBlock, field: string): Value { + const lookup = this.stateField(fn, body, field); + return body.load(lookup); + } + + public store(fn: Func, body: BasicBlock, field: string, value: Value): Value { + const lookup = this.stateField(fn, body, field); + body.store(value, lookup); + } + + public declareField(ty: Type, name: string, init: Value): void { + this.state.addField(type, name); + + this.initializers.push({ type, name, init }); + } + + public initFields(fn: Func, body: BasicBlock): void { + this.initializers.forEach((entry) => { + const field = this.stateField(fn, body, entry.name); + body.store(entry.init(entry.type, this), field); + }); + } + + public stateField(fn: Func, body: BasicBlock, name: string): Value { + const stateArg = this.stateArg(fn); + + return body.getelementptr(stateArg, INT.val(0), + INT.val(this.state.lookupField(name).index), true); + } + + public stateArg(fn: Func): Value { return fn.getArgument(ARG_STATE); } + public posArg(fn: Func): Value { return fn.getArgument(ARG_POS); } + public endPosArg(fn: Func): Value { return fn.getArgument(ARG_ENDPOS); } + public matchArg(fn: Func): Value { return fn.getArgument(ARG_MATCH); } +} diff --git a/src/llparse/compiler/compiler.ts b/src/llparse/compiler/compiler.ts new file mode 100644 index 0000000..d9447ac --- /dev/null +++ b/src/llparse/compiler/compiler.ts @@ -0,0 +1,176 @@ +import { builder } from 'bitcode'; +import { Buffer } from 'buffer'; + +import * as constants from '../constants'; +import * as node from '../node'; +import { Compilation } from './compilation'; +import * as stage from './stage'; + +const TYPE_INPUT = constants.TYPE_INPUT; +const TYPE_MATCH = constants.TYPE_MATCH; +const TYPE_ERROR = constants.TYPE_ERROR; + +const ARG_STATE = constants.ARG_STATE; +const ARG_POS = constants.ARG_POS; +const ARG_ENDPOS = constants.ARG_ENDPOS; + +const ATTR_STATE = constants.ATTR_STATE; +const ATTR_POS = constants.ATTR_POS; +const ATTR_ENDPOS = constants.ATTR_ENDPOS; + +export interface ICompilerStateProperty { + name: string; + ty: builder.types.Type; +} + +export interface ICompilerOptions { + prefix: string; + properties: ReadonlyArray; + debug: boolean; +} + +export interface ICompilerBuildResult { + bitcode: Buffer; + header: string; +} + +export class Compiler { + private readonly prefix: string; + + constructor(private readonly options: ICompilerOptions) { + this.prefix = this.options.prefix; + } + + public build(root: node.Node): ICompilerBuildResult { + const compOpts = Object.assign({}, this.options, { + root, + stages: { + before: [ + stage.NodeTranslator, + stage.MatchSequence, + stage.NodeLoopChecker, + stage.SpanAllocator, + stage.SpanBuilder + ], + after: [ + stage.NodeBuilder + ] + } + }); + + const ctx = new Compilation(compOpts); + + ctx.build(); + + const init = this.buildInit(ctx); + const execute = this.buildExecute(ctx); + + const out = { + header: '', + bitcode: null + }; + + const def = 'LLPARSE_HEADER_' + + this.prefix.toUpperCase().replace(/[^A-Z0-9]/g, '_'); + + out.header += `#ifndef ${def}\n`; + out.header += `#define ${def}\n`; + out.header += '\n'; + out.header += '#include \n'; + out.header += '\n'; + out.header += ctx.buildCState() + '\n'; + out.header += '\n'; + out.header += init + '\n'; + out.header += execute + '\n'; + out.header += '\n'; + out.header += `#endif /* ${def} */\n`; + + out.bitcode = ctx.end(); + + return out; + } + + private buildInit(ctx: Compilation): string { + const sig = ctx.ir.signature(ctx.ir.void(), [ ctx.state.ptr() ]); + const init = ctx.defineFunction(sig, this.prefix + '_init', [ ARG_STATE ]); + init.paramAttrs[0].add(ATTR_STATE); + + ctx.initFields(init, init.body); + + init.body.ret(); + + return `void ${this.prefix}_init(${this.prefix}_state_t* s);`; + } + + private buildExecute(ctx: Compilation): string { + // TODO(indutny): change signature to (state*, start*, len)? + const sig = ctx.ir.signature(TYPE_ERROR, [ + ctx.state.ptr(), + TYPE_INPUT, + TYPE_INPUT + ]); + const execute = ctx.defineFunction(sig, this.prefix + '_execute', + [ ARG_STATE, ARG_POS, ARG_ENDPOS ]); + execute.paramAttrs[0].add(ATTR_STATE); + execute.paramAttrs[1].add(ATTR_POS); + execute.paramAttrs[2].add(ATTR_ENDPOS); + + let body = execute.body; + + // Set unfinished spans + body = ctx.stageResults['span-builder'].preExecute(execute, body); + body.name = 'execute'; + + const nodeSig = ctx.signature.node; + + const currentPtr = ctx.stateField(execute, body, '_current'); + const current = body.load(currentPtr); + + const nodes = ctx.stageResults['node-builder'].map; + let callees = new Set([ ctx.stageResults['node-builder'].entry ]); + nodes.forEach((fn, node) => { + // Only nodes that can pause can be present in `_current` + node.getResumptionTargets().forEach((target) => { + callees.add(nodes.get(target)); + }); + }); + callees = Array.from(callees).map((fn) => { + return ctx.ir.metadata(fn); + }); + + const call = body.call(current, [ + ctx.stateArg(execute), + ctx.posArg(execute), + ctx.endPosArg(execute), + TYPE_MATCH.undef() + ], 'normal', constants.CCONV); + call.metadata.set('callees', ctx.ir.metadata(callees)); + + const cmp = body.icmp('ne', call, nodeSig.returnType.val(null)); + + const branch = ctx.branch(body, cmp, [ 'likely', 'unlikely' ]); + const success = branch.left; + const error = branch.right; + + // Success + success.name = 'success'; + + const bitcast = success.cast('bitcast', call, nodeSig.ptr()); + success.store(bitcast, currentPtr); + + // Invoke spans and exit + ctx.stageResults['span-builder'].postExecute(execute, success) + .ret(TYPE_ERROR.val(0)); + + // Error + error.name = 'error'; + + const errorPtr = ctx.stateField(execute, error, 'error'); + const code = error.load(errorPtr); + error.ret(code); + + return `int ${this.prefix}_execute(${this.prefix}_state_t* s, ` + + 'const char* p, const char* endp);'; + } +} +module.exports = Compiler; diff --git a/src/llparse/compiler/index.ts b/src/llparse/compiler/index.ts new file mode 100644 index 0000000..319e906 --- /dev/null +++ b/src/llparse/compiler/index.ts @@ -0,0 +1,9 @@ +import * as node from './node'; +import * as stage from './stage'; +import * as code from './code'; + +export { Compilation } from './compilation'; +export { + Compiler, ICompilerBuildResult, ICompilerOptions, ICompilerStateProperty, +} from './compiler'; +export { node, stage, code }; diff --git a/src/llparse/compiler/node/base.ts b/src/llparse/compiler/node/base.ts new file mode 100644 index 0000000..29efc34 --- /dev/null +++ b/src/llparse/compiler/node/base.ts @@ -0,0 +1,188 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import { Transform } from '../../transform'; +import * as node from './node'; +import { NodeContext } from './node-context'; +import { + Compilation, INodeID, INodePosition, BasicBlock, Func, values +} from './compilation'; + +interface ITrampoline { + block: BasicBlock; + phi?: values.instructions.Phi; +} + +export interface INodeChild { + node: Node; + noAdvance: boolean; + key: Buffer; +} + +export abstract class Node { + public name: string; + public sourceName: string; + + protected otherwise: Node | undefined; + protected skip: boolean = false; + + protected transform: Transform | undefined; + protected privNoPrologueCheck: boolean = false; + protected hasPause: boolean = false; + private trampolines: Map = new Map(); + + constructor(public readonly kind: string, id: INodeID) { + this.name = id.name; + this.sourceName = id.sourceName; + } + + public get noPrologueCheck(): boolean { return this.privNoPrologueCheck; } + + public setOtherwise(otherwise: Node, skip: boolean) { + this.otherwise = otherwise; + this.skip = skip; + } + + public getChildren(): ReadonlyArray { + return [ { node: this.otherwise, noAdvance: !this.skip, key: undefined } ]; + } + + public getResumptionTargets(): Node[] { + if (this.hasPause) { + return [ this ]; + } else { + return []; + } + } + + // Building + + public build(compilation: Compilation, nodes: Map): Func { + if (nodes.has(this)) { + return nodes.get(this)!; + } + + const fn = compilation.fn(compilation.signature.node, this.name); + const ctx = new NodeContext(compilation, this.name, fn, nodes); + + // Errors are assumed to be rarely called + // TODO(indutny): move this to node.Error somehow? + if (this instanceof node.Error) { + fn.attrs.add([ 'norecurse', 'cold', 'writeonly', 'noinline' ]); + } + + nodes.set(this, fn); + + let body = fn.body; + ctx.debug(body, 'enter'); + body = this.prologue(ctx, body); + + ctx.pos.next = body.getelementptr(ctx.pos.current, ctx.INT.val(1)); + + this.doBuild(ctx, body); + + return fn; + } + + protected prologue(ctx: Compilation, body: BasicBlock): BasicBlock { + if (this.privNoPrologueCheck) { + return body; + } + + const pos = ctx.pos.current; + const endPos = ctx.endPos; + + // Check that we have enough chars to do the read + const cmp = body.icmp('ne', pos, endPos); + + const branch = ctx.branch(body, cmp); + + // Return self when `pos === endpos` + branch.right.name = 'no_data'; + this.pause(ctx, branch.right); + + branch.left.name = 'has_data'; + return branch.left; + } + + protected pause(ctx: Compilation, body: BasicBlock): void { + const fn = ctx.fn; + const bitcast = body.cast('bitcast', fn, fn.ty.toSignature().returnType); + body.ret(bitcast); + + // To be used in `compiler.js` + this.hasPause = true; + } + + protected buildNode(ctx: Compilation, node: Node): Func { + return node.build(ctx.compilation, ctx.nodes); + } + + protected tailTo(ctx: Compilation, body: BasicBlock, pos: INodePosition, + node: Node, value?: number): Func { + const target = this.buildNode(ctx, node); + + const isCacheable = ctx.pos.next === pos; + + if (isCacheable && this.trampolines.has(target)) { + const cached = this.trampolines.get(target); + + if (cached.phi !== undefined) { + assert(value, '`.match()` and `.select()` with the same target'); + cached.phi.addEdge({ + fromBlock: body, + value: ctx.TYPE_MATCH.val(value) + }); + } else { + assert(!value, '`.match()` and `.select()` with the same target'); + } + + body.jmp(cached.trampoline); + return target; + } + + // Split, so that others could join us from code block above + const trampoline = body.parent.createBlock(body.name + '.trampoline'); + body.jmp(trampoline); + let phi = null; + + // Compute `match` if needed + if (value !== null) { + phi = trampoline.phi({ + fromBlock: body, + value: ctx.TYPE_MATCH.val(value) + }); + } + + if (isCacheable) { + this.phis.set(target, { phi, block: trampoline }); + } + + const call = trampoline.call(target, [ + ctx.state, + pos, + ctx.endPos, + phi ? phi : ctx.TYPE_MATCH.undef() + ], 'musttail'); + + trampoline.ret(call); + + return target; + } + + protected doOtherwise(ctx: Compilation, body: BasicBlock, + pos?: INodePosition): Func { + if (pos === undefined) { + pos = ctx.pos; + } + + // `.skipTo()` advances by one byte + // `.otherwise()` redirects using the same byte + const next = this.skip ? pos!.next : pos!.current; + + assert(this.otherwise); + return this.tailTo(ctx, body, next, this.otherwise); + } + + protected abstract doBuild(ctx: Compilation, body: BasicBlock); +} diff --git a/src/llparse/compiler/node/bit-check.ts b/src/llparse/compiler/node/bit-check.ts new file mode 100644 index 0000000..5d028bc --- /dev/null +++ b/src/llparse/compiler/node/bit-check.ts @@ -0,0 +1,116 @@ +import { Buffer } from 'buffer'; +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +const CHAR_WIDTH = 8; +const WORD_WIDTH = 5; + +export interface IBitCheckEntry { + node: Node; + noAdvance: boolean; + keys: Buffer[]; +} + +export class BitCheck extends Node { + public readonly entries: IBitCheckEntry[] = []; + + constructor(id: INodeID) { + super('bit-check', id); + } + + public add(entry: IBitCheckEntry): void { + this.entries.push(entry); + } + + public getChildren(): ReadonlyArray { + const res = super.getChildren(); + this.entries.forEach((entry) => { + entry.keys.forEach((key) => { + res.push({ node: entry.node, noAdvance: entry.noAdvance, key }); + }); + }); + return res; + } + + private buildTable(ctx: Compilation) { + const table = llparse.utils.buildLookupTable(WORD_WIDTH, CHAR_WIDTH, + this.entries.map(entry => entry.keys)); + + const cellTy = ctx.ir.i(1 << WORD_WIDTH); + const arrayTy = ctx.ir.array(table.table.length, cellTy); + + const global = ctx.addGlobalConst('bit_check_table', + arrayTy.val(table.table.map((elem) => cellTy.val(elem)))); + + return { + global, + cellTy, + + indexShift: table.indexShift, + shiftMask: table.shiftMask, + shiftMul: table.shiftMul, + valueMask: table.valueMask + }; + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + const pos = ctx.pos.current; + + // Load the character + let current = body.load(pos); + + // Transform the character if needed + if (this.transform) { + const res = ctx.compilation.buildTransform(this.transform, + body, current); + body = res.body; + current = res.current; + } + + // Build global lookup table + const table = this.buildTable(ctx); + + const cellTy = table.cellTy; + + const index = body.binop('lshr', current, + pos.ty.toPointer().to.val(table.indexShift)); + + let shift = body.binop('and', current, + pos.ty.toPointer().to.val(table.shiftMask)); + shift = body.cast('zext', shift, cellTy); + + // Compiler can handle it, but why generate more code? + if (table.shiftMul !== 1) { + shift = body.binop('mul', shift, cellTy.val(table.shiftMul)); + } + + const ptr = body.getelementptr(table.global, ctx.INT.val(0), + index, true); + const load = body.load(ptr); + + const shr = body.binop('lshr', load, shift); + const masked = body.binop('and', shr, cellTy.val(table.valueMask)); + + const weights = new Array(this.entries.length + 1).fill('likely'); + + this.entries.forEach((entry, i) => { + if (entry.node instanceof node.Error) + weights[i + 1] = 'unlikely'; + }); + + if (this.otherwise instanceof node.Error) + weights[0] = 'unlikely'; + + const keys = this.entries.map((entry, i) => i + 1); + const s = ctx.buildSwitch(body, masked, keys, weights); + + s.cases.forEach((body, i) => { + const child = this.entries[i]; + + this.tailTo(ctx, body, child.noAdvance ? ctx.pos.current : ctx.pos.next, + child.node, null); + }); + + this.doOtherwise(ctx, s.otherwise); + } +} diff --git a/src/llparse/compiler/node/consume.ts b/src/llparse/compiler/node/consume.ts new file mode 100644 index 0000000..744a533 --- /dev/null +++ b/src/llparse/compiler/node/consume.ts @@ -0,0 +1,51 @@ +import * as assert from 'assert'; +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class Consume extends Node { + constructor(id: INodeID, private readonly fieldName: string) { + super('consume', id); + this.privNoPrologueCheck = true; + } + + public doBuild(ctx: Compilation, body: BasicBlock): void { + const INVARIANT_GROUP = ctx.INVARIANT_GROUP; + + const pos = ctx.pos.current; + + const indexPtr = ctx.stateField(body, this.fieldName); + const index = body.load(indexPtr); + index.metadata.set('invariant.group', INVARIANT_GROUP); + + const need = ctx.truncate(body, index, ctx.TYPE_INTPTR); + + const intPos = body.cast('ptrtoint', pos, ctx.TYPE_INTPTR); + const intEndPos = body.cast('ptrtoint', ctx.endPos, ctx.TYPE_INTPTR); + + const avail = body.binop('sub', intEndPos, intPos); + const cmp = body.icmp('uge', avail, need); + const branch = ctx.branch(body, cmp, [ 'likely', 'unlikely' ]); + + const hasData = branch.left; + const noData = branch.right; + hasData.name = 'has_data'; + noData.name = 'no_data'; + + // Continue! + const next = hasData.getelementptr(pos, index); + + assert(!this.skip); + hasData.store(index.ty.val(0), indexPtr) + .metadata.set('invariant.group', INVARIANT_GROUP); + this.doOtherwise(ctx, hasData, { current: next, next: null }); + + // Pause! + const left = noData.binop('sub', need, avail); + + const leftTrunc = ctx.truncate(noData, left, index.ty); + + noData.store(leftTrunc, indexPtr) + .metadata.set('invariant.group', INVARIANT_GROUP); + this.pause(ctx, noData); + } +} diff --git a/src/llparse/compiler/node/empty.ts b/src/llparse/compiler/node/empty.ts new file mode 100644 index 0000000..1f3d0e7 --- /dev/null +++ b/src/llparse/compiler/node/empty.ts @@ -0,0 +1,18 @@ +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class Empty extends Node { + constructor(id: INodeID) { + super('empty', id); + } + + protected prologue(ctx: Compilation, body: BasicBlock): BasicBlock { + if (this.skip) + return super.prologue(ctx, body); + return body; + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + this.doOtherwise(ctx, body); + } +} diff --git a/src/llparse/compiler/node/error.ts b/src/llparse/compiler/node/error.ts new file mode 100644 index 0000000..af0159a --- /dev/null +++ b/src/llparse/compiler/node/error.ts @@ -0,0 +1,46 @@ +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class Error extends Node { + constructor(id: INodeID, private readonly code: number, + private readonly reason: string) { + super('error', id); + + this.privNoPrologueCheck = true; + } + + public getChildren(): ReadonlyArray { + return []; + } + + private buildStoreError(ctx: Compilation, body: BasicBlock): BasicBlock { + const INT = ctx.INT; + + const reason = ctx.cstring(this.reason); + + const codePtr = ctx.stateField(body, 'error'); + const reasonPtr = ctx.stateField(body, 'reason'); + const posPtr = ctx.stateField(body, 'error_pos'); + + const cast = body.getelementptr(reason, INT.val(0), INT.val(0), true); + + body.store(ctx.TYPE_ERROR.val(this.code), codePtr); + body.store(cast, reasonPtr); + body.store(ctx.pos.current, posPtr); + + return body; + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + body = this.buildStoreError(ctx, body); + + const currentPtr = ctx.stateField(body, '_current'); + + // Non-recoverable state + const nodeSig = ctx.compilation.signature.node; + body.store(nodeSig.ptr().val(null), currentPtr); + + const retType = ctx.fn.ty.toSignature().returnType; + body.ret(retType.val(null)); + } +} diff --git a/src/llparse/compiler/node/index.ts b/src/llparse/compiler/node/index.ts new file mode 100644 index 0000000..dcfd062 --- /dev/null +++ b/src/llparse/compiler/node/index.ts @@ -0,0 +1,11 @@ +export { Node } from './base'; +export { Empty } from './empty'; +export { Error } from './error'; +export { Invoke } from './invoke'; +export { Single } from './single'; +export { Sequence } from './sequence'; +export { SpanStart } from './span-start'; +export { SpanEnd } from './span-end'; +export { Consume } from './consume'; +export { Pause } from './pause'; +export { BitCheck } from './bit-check'; diff --git a/src/llparse/compiler/node/invoke.ts b/src/llparse/compiler/node/invoke.ts new file mode 100644 index 0000000..c8e117e --- /dev/null +++ b/src/llparse/compiler/node/invoke.ts @@ -0,0 +1,64 @@ +import * as assert from 'assert'; + +import { Code } from '../code'; +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class Invoke extends Node { + private readonly map: Map = new Map(); + + constructor(id: INodeID, private readonly code: Code) { + super('invoke', id); + + this.code = code; + this.privNoPrologueCheck = true; + } + + public add(key: number, node: Node): void { + assert(!this.map.has(key)); + this.map.set(key, node); + } + + public getChildren(): ReadonlyArray { + return super.getChildren().concat(this.map.forEach((node) => { + return { node, noAdvance: true, key: null }; + })); + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + const code = ctx.compilation.buildCode(this.code); + + const args = [ + ctx.state, + ctx.pos.current, + ctx.endPos + ]; + + if (this.code[kSignature] === 'value') + args.push(ctx.match); + else + assert.strictEqual(this.code[kSignature], 'match'); + + const call = body.call(code, args); + + const keys = Object.keys(this.map).map(key => key | 0); + + const weights = new Array(1 + keys.length).fill('likely'); + + // Mark error branches as unlikely + keys.forEach((key, i) => { + if (this.map[key] instanceof node.Error) + weights[i + 1] = 'unlikely'; + }); + + if (this.otherwise instanceof node.Error) + weights[0] = 'unlikely'; + + const s = ctx.buildSwitch(body, call, keys, weights); + s.cases.forEach((body, i) => { + this.tailTo(ctx, body, ctx.pos.current, this.map[keys[i]]); + }); + + this.doOtherwise(ctx, s.otherwise); + } +} diff --git a/src/llparse/compiler/node/node-context.ts b/src/llparse/compiler/node/node-context.ts new file mode 100644 index 0000000..81b1658 --- /dev/null +++ b/src/llparse/compiler/node/node-context.ts @@ -0,0 +1,64 @@ +import { Builder } from 'bitcode'; + +import { + BasicBlock, Compilation, INodePosition, Func, types, values, +} from '../compilation'; +import { Node } from './base'; + +export class NodeContext { + // Some generally useful types (to avoid unnecessary includes) + public readonly INT = constants.INT; + public readonly BOOL = constants.BOOL; + public readonly TYPE_MATCH = constants.TYPE_MATCH; + public readonly TYPE_REASON = constants.TYPE_REASON; + public readonly TYPE_ERROR = constants.TYPE_ERROR; + public readonly TYPE_INDEX = constants.TYPE_INDEX; + public readonly TYPE_INTPTR = constants.TYPE_INTPTR; + public readonly state: values.Value; + public readonly pos: INodePosition; + public readonly endPos: values.Value; + public readonly match: values.Value; + public readonly ir: Builder; + public readonly stateType: types.Struct; + public readonly signature: { [key: string]: types.Signature }; + public readonly INVARIANT_GROUP: values.constants.Metadata; + + constructor(public readonly compilation: Compilation, + public readonly name: string, + public readonly fn: Func, + public readonly nodes: Map) { + this.state = this.compilation.stateArg(this.fn); + this.pos = { + current: this.compilation.posArg(this.fn), + next: undefined + }; + this.endPos = this.compilation.endPosArg(this.fn); + this.match = this.compilation.matchArg(this.fn); + + // Re-export some properties + this.ir = this.compilation.ir; + this.signature = this.compilation.signature; + this.stateType = this.compilation.state; + + this.INVARIANT_GROUP = ctx.INVARIANT_GROUP; + } + + public debug(body: BasicBlock, message: string): void { + this.compilation.debug(this.fn, body, `${this.name}: ${message}`); + } + + // Just proxy + public stateField(body: BasicBlock, name: string): values.Value { + return this.compilation.stateField(this.fn, body, name); + } + + public cstring(...args) { return this.compilation.cstring(...args); } + public blob(...args) { return this.compilation.blob(...args); } + public addGlobalConst(...args) { + return this.compilation.addGlobalConst(...args); + } + public truncate(...args) { return this.compilation.truncate(...args); } + public call(...args) { return this.compilation.call(...args); } + public buildSwitch(...args) { return this.compilation.buildSwitch(...args); } + public branch(...args) { return this.compilation.branch(...args); } +} diff --git a/src/llparse/compiler/node/pause.ts b/src/llparse/compiler/node/pause.ts new file mode 100644 index 0000000..e04b0a8 --- /dev/null +++ b/src/llparse/compiler/node/pause.ts @@ -0,0 +1,25 @@ +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; +import { Error } from './error'; + +export class Pause extends Error { + constructor(id: INodeID, code: number, reason: string) { + super(id, code, reason); + } + + public getResumptionTargets(): ReadonlyArray { + return super.getResumptionTargets().concat(this.otherwise); + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + body = this.buildStoreError(ctx, body); + + const currentPtr = ctx.stateField(body, '_current'); + + // Recoverable state + const target = this.buildNode(ctx, this.otherwise); + body.store(target, currentPtr); + + body.ret(ctx.fn.ty.toSignature().returnType.val(null)); + } +} diff --git a/src/llparse/compiler/node/sequence.ts b/src/llparse/compiler/node/sequence.ts new file mode 100644 index 0000000..9460bcc --- /dev/null +++ b/src/llparse/compiler/node/sequence.ts @@ -0,0 +1,87 @@ +import { Buffer } from 'buffer'; + +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; +import { + TYPE_INDEX, SEQUENCE_COMPLETE, SEQUENCE_PAUSE, SEQUENCE_MISMATCH, +} from '../../constants'; + +export class Sequence extends Node { + private next: Node | undefined; + private value: number | undefined; + + constructor(id: INodeID, private readonly select: Buffer) { + super('sequence', id); + } + + public finish(next: Node, value: number) { + this.next = next; + this.value = value; + } + + public getChildren(): ReadonlyArray { + return super.getChildren().concat({ + node: this.next, + noAdvance: false, + key: this.select + }); + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + const INT = ctx.INT; + + const seq = ctx.blob(this.select); + + const cast = body.getelementptr(seq, INT.val(0), INT.val(0), true); + + const matchSequence = ctx.compilation.stageResults['match-sequence'] + .get(this.transform); + + const returnType = matchSequence.ty.toSignature().returnType; + + const call = body.call(matchSequence, [ + ctx.state, + ctx.pos.current, + ctx.endPos, + cast, + TYPE_INDEX.val(seq.ty.toPointer().to.length) + ]); + + const status = body.extractvalue(call, + returnType.lookupField('status').index); + const current = body.extractvalue(call, + returnType.lookupField('current').index); + + // This is lame, but it is easier to do it this way + // (Optimizer will remove it, if it isn't needed) + const next = body.getelementptr(current, INT.val(1)); + + const s = ctx.buildSwitch(body, status, [ + SEQUENCE_COMPLETE, + SEQUENCE_PAUSE, + SEQUENCE_MISMATCH + ], [ + 'unlikely', // default + + 'likely', + 'unlikely', // pause + this.otherwise instanceof node.Error ? 'unlikely' : 'likely' + ]); + + // No other values are allowed + s.otherwise.unreachable(); + + const complete = s.cases[0]; + const pause = s.cases[1]; + const mismatch = s.cases[2]; + complete.name = 'complete'; + pause.name = 'pause'; + mismatch.name = 'mismatch'; + + this.tailTo(ctx, complete, next, this.next!, this.value!); + this.pause(ctx, pause); + + // Not equal + this.doOtherwise(ctx, mismatch, { current, next }); + } +} diff --git a/src/llparse/compiler/node/single.ts b/src/llparse/compiler/node/single.ts new file mode 100644 index 0000000..e24e782 --- /dev/null +++ b/src/llparse/compiler/node/single.ts @@ -0,0 +1,56 @@ +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class Single extends Node { + private readonly children: INodeChild[] = []; + + constructor(id: INodeID) { + super('single', id); + } + + public add(child: INodeChild): void { + this.children.push(child); + } + + public getChildren(): ReadonlyArray { + return super.getChildren().concat(this.children); + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + const pos = ctx.pos.current; + + // Load the character + let current = body.load(pos); + + // Transform the character if needed + if (this.transform) { + const res = ctx.compilation.buildTransform(this.transform, + body, current); + body = res.body; + current = res.current; + } + + const weights = new Array(1 + this.children.length).fill('likely'); + + // Mark error branches as unlikely + this.children.forEach((child, i) => { + if (child.node instanceof node.Error) + weights[i + 1] = 'unlikely'; + }); + + if (this.otherwise instanceof node.Error) + weights[0] = 'unlikely'; + + const keys = this.children.map(child => child.key); + const s = ctx.buildSwitch(body, current, keys, weights); + + s.cases.forEach((body, i) => { + const child = this.children[i]; + + this.tailTo(ctx, body, child.noAdvance ? ctx.pos.current : ctx.pos.next, + child.node, child.value); + }); + + this.doOtherwise(ctx, s.otherwise); + } +} diff --git a/src/llparse/compiler/node/span-end.ts b/src/llparse/compiler/node/span-end.ts new file mode 100644 index 0000000..69b79cb --- /dev/null +++ b/src/llparse/compiler/node/span-end.ts @@ -0,0 +1,22 @@ +import { Code } from '../code'; +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class SpanEnd extends Node { + constructor(id: INodeID, private readonly code: Code) { + super('span-end', id); + + this.privNoPrologueCheck = true; + } + + public getResumptionTargets(): ReadonlyArray { + return super.getResumptionTargets().concat(this.otherwise); + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + const result = ctx.compilation.stageResults['span-builder'].spanEnd( + ctx.fn, body, this.code); + + result.updateResumptionTarget(this.doOtherwise(ctx, result.body)); + } +} diff --git a/src/llparse/compiler/node/span-start.ts b/src/llparse/compiler/node/span-start.ts new file mode 100644 index 0000000..9bf0d3c --- /dev/null +++ b/src/llparse/compiler/node/span-start.ts @@ -0,0 +1,21 @@ +import { Code } from '../code'; +import { Compilation, BasicBlock, INodeID } from '../compilation'; +import { Node, INodeChild } from './base'; + +export class SpanStart extends Node { + constructor(id: INodeID, private readonly code: Code) { + super('span-start', id); + + this.privNoPrologueCheck = true; + } + + public getResumptionTargets(): ReadonlyArray { + return super.getResumptionTargets().concat(this.otherwise); + } + + protected doBuild(ctx: Compilation, body: BasicBlock): void { + body = ctx.compilation.stageResults['span-builder'].spanStart( + ctx.fn, body, this.code); + this.doOtherwise(ctx, body); + } +} diff --git a/src/llparse/compiler/stage/base.ts b/src/llparse/compiler/stage/base.ts new file mode 100644 index 0000000..aee2399 --- /dev/null +++ b/src/llparse/compiler/stage/base.ts @@ -0,0 +1,10 @@ +import { Compilation } from './compilation'; + +export abstract class Stage { + constructor(protected readonly ctx: Compilation, + public readonly name: string) { + } + + // TODO(indutny): specify types? + public abstract build(): any; +} diff --git a/src/llparse/compiler/stage/index.ts b/src/llparse/compiler/stage/index.ts new file mode 100644 index 0000000..15e8a63 --- /dev/null +++ b/src/llparse/compiler/stage/index.ts @@ -0,0 +1,8 @@ +export { Stage } from './base'; + +export { MatchSequence } from './match-sequence'; +export { NodeTranslator } from './node-translator'; +export { NodeLoopChecker } from './node-loop-checker'; +export { NodeBuilder } from './node-builder'; +export { SpanAllocator } from './span-allocator'; +export { SpanBuilder } from './span-builder'; diff --git a/src/llparse/compiler/stage/match-sequence.ts b/src/llparse/compiler/stage/match-sequence.ts new file mode 100644 index 0000000..8b61b5d --- /dev/null +++ b/src/llparse/compiler/stage/match-sequence.ts @@ -0,0 +1,197 @@ +import { + CCONV, + INT, TYPE_INPUT, TYPE_INDEX, TYPE_STATUS, + ATTR_STATE, ATTR_POS, ATTR_ENDPOS, ATTR_SEQUENCE, + + ARG_STATE, ARG_POS, ARG_ENDPOS, ARG_SEQUENCE, ARG_SEQUENCE_LEN, + + SEQUENCE_COMPLETE, SEQUENCE_PAUSE, SEQUENCE_MISMATCH +} from '../../constants'; +import { Transform } from '../../transform'; +import { + Compilation, BasicBlock, INodePosition, Func, values, +} from '../compilation'; +import { Stage } from './base'; + +interface IIterationResult { + index: values.Value; + pos: INodePosition; + complete: BasicBlock; + mismatch: BasicBlock; + loop: BasicBlock; + pause: BasicBlock; +} + +export class MatchSequence extends Stage { + private readonly cache: Map = new Map(); + + constructor(ctx: Compilation) { + super(ctx, 'match-sequence'); + + this.returnType = this.ctx.ir.struct('match_sequence_ret'); + this.returnType.addField(TYPE_INPUT, 'current'); + this.returnType.addField(TYPE_STATUS, 'status'); + this.returnType.finalize(); + + this.signature = this.ctx.ir.signature(this.returnType, [ + this.ctx.state.ptr(), + TYPE_INPUT, + TYPE_INPUT, + TYPE_INPUT, + TYPE_INDEX + ]); + } + + public build(): any { + return { + get: transform => this.get(transform) + }; + } + + public get(transform?: Transform): Func { + const cacheKey = transform === undefined ? undefined : transform.name; + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey)!; + } + + const postfix = cacheKey ? '_' + cacheKey.toLowerCase() : ''; + + const fn = this.ctx.defineFunction(this.signature, + `${this.ctx.prefix}__match_sequence${postfix}`, + [ ARG_STATE, ARG_POS, ARG_ENDPOS, ARG_SEQUENCE, ARG_SEQUENCE_LEN ]); + + fn.paramAttrs[0].add(ATTR_STATE); + fn.paramAttrs[1].add(ATTR_POS); + fn.paramAttrs[2].add(ATTR_ENDPOS); + fn.paramAttrs[3].add(ATTR_SEQUENCE); + + this.buildBody(fn, transform); + + fn.linkage = 'internal'; + fn.cconv = CCONV; + fn.attrs.add([ 'nounwind', 'norecurse', 'alwaysinline' ]); + + this.cache.set(cacheKey, fn); + + return fn; + } + + private buildBody(fn: Func, transform?: Transform): void { + const body = fn.body; + + const maxSeqLen = this.ctx.stageResults['node-translator'].maxSequenceLen; + const range = this.ctx.ir.metadata([ + this.ctx.ir.metadata(TYPE_INDEX.val(0)), + this.ctx.ir.metadata(TYPE_INDEX.val(maxSeqLen)) + ]); + + // Just to have a label + const start = body.parent.createBlock('start'); + body.jmp(start); + + // Load `state.index` + const indexPtr = this.ctx.stateField(fn, start, '_index'); + const index = start.load(indexPtr); + index.metadata.set('invariant.group', this.ctx.INVARIANT_GROUP); + index.metadata.set('range', range); + + // Loop start + const loop = body.parent.createBlock('loop'); + start.jmp(loop); + + const posPhi = loop.phi({ fromBlock: start, value: this.ctx.posArg(fn) }); + const indexPhi = loop.phi({ fromBlock: start, value: index }); + + const iteration = this.buildIteration(fn, loop, indexPtr, posPhi, indexPhi, + transform); + + // It is complete! + this.ret(iteration.complete, iteration.pos, SEQUENCE_COMPLETE); + + // We have more data! + iteration.loop.jmp(loop); + indexPhi.addEdge({ fromBlock: iteration.loop, value: iteration.index }); + posPhi.addEdge({ fromBlock: iteration.loop, value: iteration.pos.next }); + + // Have to pause - return self + this.ret(iteration.pause, iteration.pos, SEQUENCE_PAUSE); + + // Not equal + this.ret(iteration.mismatch, iteration.pos, SEQUENCE_MISMATCH); + } + + private buildIteration(fn: Func, body: BasicBlock, indexField: values.Value, + pos: values.Value, index: values.Value, + transform?: Transform): IIterationResult { + const seq = fn.getArgument(ARG_SEQUENCE); + const seqLen = fn.getArgument(ARG_SEQUENCE_LEN); + + let current = body.load(pos); + + // Transform the character if needed + if (transform) { + const res = this.ctx.buildTransform(transform, + body, current); + body = res.body; + current = res.current; + } + + const expectedPtr = body.getelementptr(seq, index); + const expected = body.load(expectedPtr); + + // NOTE: fetch this early so it would dominate all returns + const next = body.getelementptr(pos, INT.val(1)); + + let cmp = body.icmp('eq', current, expected); + const isMatch = fn.createBlock('match'); + const isMismatch = fn.createBlock('mismatch'); + body.branch(cmp, isMatch, isMismatch); + + // Mismatch + this.reset(isMismatch, indexField); + + // Character matches + const index1 = isMatch.binop('add', index, TYPE_INDEX.val(1)); + cmp = isMatch.icmp('eq', index1, seqLen); + + const isComplete = fn.createBlock('is_complete'); + const isIncomplete = fn.createBlock('is_incomplete'); + isMatch.branch(cmp, isComplete, isIncomplete); + + this.reset(isComplete, indexField); + + cmp = isIncomplete.icmp('ne', next, this.ctx.endPosArg(fn)); + const { left: moreData, right: noMoreData } = + this.ctx.branch(isIncomplete, cmp, [ 'likely', 'unlikely' ]); + + moreData.name = 'more_data'; + + noMoreData.name = 'no_more_data'; + const store = noMoreData.store(index1, indexField); + store.metadata.set('invariant.group', this.ctx.INVARIANT_GROUP); + + return { + index: index1, + pos: { current: pos, next }, + complete: isComplete, + mismatch: isMismatch, + loop: moreData, + pause: noMoreData + }; + } + + private ret(body: BasicBlock, pos: INodePosition, status: number): void { + const create = body.insertvalue(this.returnType.undef(), pos.current, + this.returnType.lookupField('current').index); + + const amend = body.insertvalue(create, TYPE_STATUS.val(status), + this.returnType.lookupField('status').index); + + body.ret(amend); + } + + private reset(body: BasicBlock, field: values.Value): void { + const store = body.store(TYPE_INDEX.val(0), field); + store.metadata.set('invariant.group', this.ctx.INVARIANT_GROUP); + } +} diff --git a/src/llparse/compiler/stage/node-builder.ts b/src/llparse/compiler/stage/node-builder.ts new file mode 100644 index 0000000..6b337c0 --- /dev/null +++ b/src/llparse/compiler/stage/node-builder.ts @@ -0,0 +1,20 @@ +import { Compilation, Func } from '../compilation'; +import { Node } from '../node'; +import { Stage } from './base'; + +export class NodeBuilder extends Stage { + private readonly nodes: Map = new Map(); + + constructor(ctx: Compilation) { + super(ctx, 'node-builder'); + } + + public build(): any { + // TODO(indutny): types + const root = this.ctx.stageResults['node-translator'].root; + return { + entry: root.build(this.ctx, this.nodes), + map: this.nodes + }; + } +} diff --git a/src/llparse/compiler/stage/node-loop-checker.ts b/src/llparse/compiler/stage/node-loop-checker.ts new file mode 100644 index 0000000..276edbb --- /dev/null +++ b/src/llparse/compiler/stage/node-loop-checker.ts @@ -0,0 +1,90 @@ +import { Compilation } from '../compilation'; +import { Node } from '../node'; +import { Stage } from './base'; + +interface IQueueElem { + node: Node; + key: number | Buffer | undefined; +} + +export class NodeLoopChecker extends Stage { + private readonly reachable: Map > = new Map(); + + constructor(ctx: Compilation) { + super(ctx, 'node-loop-checker'); + } + + public build(): any { + const queue: IQueueElem = [ { + node: this.ctx.stageResults['node-translator'].root, + key: undefined + } ]; + + while (queue.length !== 0) { + const item = queue.pop(); + const lastKey = item.key; + const node = item.node; + + let children = node.getChildren(); + + // Loops like: + // + // `nodeA: peek(A)` => `nodeB: match(A), otherwise -> nodeA` + // + // should pass the check + if (typeof lastKey === 'number') { + // Remove all unreachable clauses + children = children.filter((child) => { + return child.key === undefined || child.key === lastKey; + }); + + // See if there is a matching peek clause + const sameKey = children.some((child) => { + return child.key === lastKey && !child.noAdvance; + }); + if (sameKey) + return; + } + + children = children.filter(child => child.noAdvance); + + children.forEach((child) => { + if (this.addEdge(node, child.node)) { + queue.push({ node: child.node, key: child.key || lastKey }); + } + }); + } + + return true; + } + + private reachable(from: Node): Set { + if (this.reachableMap.has(from)) { + return this.reachableMap.get(from)!; + } + + const res = new Set([ from ]); + this.reachableMap.set(from, res); + return res; + } + + private addEdge(from: Node, to: Node): boolean { + const target = this.reachable(to); + + let changed = false; + this.reachable(from).forEach((node) => { + if (to === node) { + throw new Error(`Loop detected in "${to.sourceName}", ` + + `through the backedge from "${from.sourceName}"`); + } + + if (target.has(node)) + return; + + changed = true; + target.add(node); + }); + + return changed; + } +} diff --git a/src/llparse/compiler/stage/node-translator.ts b/src/llparse/compiler/stage/node-translator.ts new file mode 100644 index 0000000..6e66b0b --- /dev/null +++ b/src/llparse/compiler/stage/node-translator.ts @@ -0,0 +1,296 @@ +import * as assert from 'assert'; +import * as debug from 'debug'; + +import * as node from '../../node'; +import { Trie, TrieNode, TrieSingle, TrieSequence, TrieNext } from '../../trie'; +import { Compilation } from '../compilation'; +import * as compilerNode from '../node'; +import { Stage } from './base'; + +const debugOpt = debug('llparse:opt'); + +type TrieOutputList = compilerNode[]; + +export interface INodeTranslatorOptions { + // Minimum number of cases of `single` node to make it eligable for + // `BitCheck` optimization + minCheckSize: number | undefined; + + // Maximum width of entry in a bitfield for a `BitCheck` optimization + maxCheckWidth: number | undefined; +} + +export class NodeTranslator extends Stage { + private readonly options: INodeTranslatorOptions; + private readonly nodes: Map = new Map(); + private readonly errorCache: Map = new Map(); + private maxSequenceLen: number = 0; + + constructor(ctx: Compilation, options: INodeTranslatorOptions) { + super(ctx, 'node-translator'); + + this.options = Object.assign({ + minCheckSize: llparse.constants.DEFAULT_TRANSLATOR_MIN_CHECK_SIZE, + + maxCheckWidth: llparse.constants.DEFAULT_TRANSLATOR_MAX_CHECK_WIDTH + }, options); + } + + public build(): any { + return { + root: this.buildNode(this.ctx.root, undefined), + maxSequenceLen: this.maxSequenceLen + }; + } + + private id(node: node.Node, postfix: string = '') { + return this.ctx.id(node.name, 'n_', postfix); + } + + private buildNode(node: node.Node, value: number | undefined) { + this.checkSignature(node, value); + + if (this.nodes.has(node)) { + return this.nodes.get(node); + } + + let res; + let list: TrieOutputList | undefined; + let last; + if (node instanceof node.Invoke) { + res = new compilerNode.Invoke(this.id(node), node.code); + } else if (node instanceof node.Error) { + res = this.buildError(node); + } else if (node instanceof node.SpanStart) { + res = new compilerNode.SpanStart(this.id(node), node.code); + } else if (node instanceof node.SpanEnd) { + res = new compilerNode.SpanEnd(this.id(node), node.code); + } else if (node instanceof node.Pause) { + res = new compilerNode.Pause(this.id(node), node.code, node.reason); + } else if (node instanceof node.Consume) { + res = new compilerNode.Consume(this.id(node), node.fieldName); + } else { + assert(node instanceof node.Match); + + const trie = new Trie(node.name); + + const combined = trie.combine(node.cases); + if (combined === null) { + res = new compilerNode.Empty(this.id(node)); + } else { + list = []; + res = this.buildTrie(node, combined, list, true); + } + } + + if (!last) + last = res; + + // Prevent loops + this.nodes.set(node, res); + + // Build `invoke`'s map + if (node instanceof llparse.node.Invoke) { + Object.keys(node.map).forEach((key) => { + res.add(key, this.buildNode(node.map[key], undefined)); + }); + } + + if (node instanceof llparse.node.Error) { + assert.strictEqual(node.getOtherwise(), undefined); + } else { + const otherwise = node.getOtherwise(); + assert.notStrictEqual(node.otherwise, undefined, + `Node "${node.name}" must have \`.otherwise()\`/\`.skipTo()\``); + + if (!list) + list = [ last ]; + const otherwise = this.buildNode(otherwise.next, null); + list.forEach((entry) => { + entry.setOtherwise(otherwise, otherwise.skip); + }); + } + + // Do a "peephole"-like optimization. If there are empty nodes that do + // not skip over the input, and lead to nodes with prologue check + // (p != endp) - stitch the node straight to its `otherwise` recursively. + for (;;) { + if (!(res instanceof compilerNode.Empty)) + break; + if (res.skip) + break; + + const otherwise = res.otherwise; + // Since we're running in de-looped mode, sometimes `otherwise` may not + // be set yet. + if (!otherwise || otherwise.noPrologueCheck) + break; + + res = otherwise; + } + + return res; + } + + private buildError(node: node.Error): complerNode.Error { + const cacheKey = (node.code >>> 0) + ':' + node.reason; + if (this.errorCache.has(cacheKey)) + return this.errorCache.get(cacheKey); + + const res = new compilerNode.Error(this.id(node), node.code, node.reason); + this.errorCache.set(cacheKey, res); + return res; + } + + private buildTrie(node: node.Match, trie: TrieNode, list: TrieOutputList, + isRoot: boolean = false): compilerNode.Node { + if (trie instanceof TrieNext) { + assert(!isRoot); + return this.buildNode(trie.next, trie.value); + } + + if (trie instanceof TrieSingle) { + return this.buildSingle(node, trie, list, isRoot); + } + + if (trie instanceof TrieSequence) { + return this.buildSequence(node, trie, list, isRoot); + } + + throw new Error('Unknown trie node'); + } + + private buildSingle(node: node.Match, trie: TrieSingle, list: TrieOutputList, + isRoot: boolean): compilerNode.Node { + assert.notStrictEqual(trie.children.length, 0); + + // Fast case, every child leads to the same result and no values are passed + const bitCheck = this.buildBitCheck(node, trie, list, isRoot); + if (bitCheck) { + return bitCheck; + } + + const res = new compiler.node.Single(this.id(node)); + + // Break loops + if (isRoot) { + this.nodes.set(node, res); + } + + res.transform = node.getTransform(); + res.children = trie.children.map((child) => { + return { + key: child.key, + next: this.buildTrie(node, child.child, list), + noAdvance: child.noAdvance, + value: child.child.value + }; + }); + + list.push(res); + return res; + } + + private buildBitCheck(node: node.Match, trie: TrieSingle, + list: TrieOutputList, isRoot: boolean) + : compilerNode.Node | false { + if (trie.children.length < this.options.minCheckSize) { + return false; + } + + const targets = new Map(); + + const bailout = !trie.children.every((child) => { + if (child.child.value !== null) { + return false; + } + + if (child.child.type !== 'next') { + return false; + } + + const target = child.child.next; + if (!targets.has(target)) { + targets.set(target, { + noAdvance: child.noAdvance, + keys: [ child.key ], + trie: child.child + }); + return true; + } + + const existing = targets.get(target); + + // TODO(indutny): just use it as a sub-key? + if (existing.noAdvance !== child.noAdvance) { + debugOpt('Can\'t transform single to bitcheck due to ' + + '`.peek()`/`.match()` conflict'); + return false; + } + + existing.keys.push(child.key); + return true; + }); + + if (bailout) { + return false; + } + + // We've width limit for this optimization + if (targets.size >= (1 << this.options.maxCheckWidth)) { + debugOpt('Can\'t transform single to bitcheck due to ' + + 'large distinct target count=%d', targets.size); + return false; + } + + const res = new compiler.node.BitCheck(this.id(node)); + + // Break loops + if (isRoot) { + this.nodes.set(node, res); + } + + res.transform = node.getTransform(); + + const map = []; + let totalKeys = 0; + targets.forEach((info) => { + const next = this.buildTrie(node, info.trie, list); + totalKeys += info.keys.length; + map.push({ node: next, noAdvance: info.noAdvance, keys: info.keys }); + }); + res.map = map; + + debugOpt('Transformed Single into BitCheck targets.len=%d total_keys=%d', + res.map.length, totalKeys); + + list.push(res); + return res; + } + + private buildSequence(node: node.Match, trie: TrieSequence, + list: TrieOutputList, isRoot: boolean) + : compilerNode.Node { + const res = new compiler.node.Sequence(this.id(node), trie.select); + const value = trie.child.type === 'next' ? trie.child.value : null; + + this.maxSequenceLen = Math.max(this.maxSequenceLen, trie.select.length); + + // Break loops + if (isRoot) { + this.nodes.set(node, res); + } + + res.transform = node.getTransform(); + res.next = this.buildTrie(node, trie.child, list); + res.value = value; + + list.push(res); + return res; + } + + private checkSignature(node: node.Node, value: number | undefined) { + assert.strictEqual(node.signature, + value === undefined ? 'match' : 'value'); + } +} diff --git a/src/llparse/compiler/stage/span-allocator.ts b/src/llparse/compiler/stage/span-allocator.ts new file mode 100644 index 0000000..48ffaa7 --- /dev/null +++ b/src/llparse/compiler/stage/span-allocator.ts @@ -0,0 +1,173 @@ +import { Code } from '../code'; +import { Compilation } from '../compilation'; +import { Node } from '../node'; +import { Stage } from './base'; + +export type SpanID = Code; + +interface IActiveResult { + active: activeMap; + spans: ReadonlyArray; +} + +type OverlapMap = Map >; + +export type ColoringMap = ReadonlyMap; + +export interface IColoringResult { + map: ColoringMap; + concurrency: ReadonlyArray; + max: number; +} + +export class Allocator extends Stage { + constructor(ctx: Compilation) { + super(ctx, 'span-allocator'); + } + + private id(node): SpanID { + return node.code; + } + + public build(): any { + const root = this.ctx.stageResults['node-translator'].root; + const nodes = this.getNodes(root); + const info = this.computeActive(nodes); + const overlap = this.computeOverlap(info); + const color = this.color(info.spans, overlap); + + return color; + } + + private getNodes(root: Node): ReadonlyArray { + const res = new Set(); + const queue = [ root ]; + while (queue.length !== 0) { + const node = queue.pop(); + if (res.has(node)) + continue; + res.add(node); + + node.getChildren().forEach(child => queue.push(child.node)); + } + return Array.from(res); + } + + private computeActive(nodes: ReadonlyArray): IActiveResult { + const activeMap = new Map(); + nodes.forEach(node => activeMap.set(node, new Set())); + + const queue = new Set(nodes); + const spans = new Set(); + while (queue.size !== 0) { + const node = queue.values().next().value; + queue.delete(node); + + const active = activeMap.get(node); + + if (node instanceof compiler.node.SpanStart) { + const span = this.id(node); + spans.add(span); + active.add(span); + } + + active.forEach((span) => { + // Don't propagate span past the spanEnd + if (node instanceof compiler.node.SpanEnd && span === this.id(node)) + return; + + node.getChildren().forEach((child) => { + const node = child.node; + + // Disallow loops + if (node instanceof compiler.node.SpanStart) { + assert.notStrictEqual(this.id(node), span, + `Detected loop in span "${span.name}"`); + } + + const set = activeMap.get(node); + if (set.has(span)) + return; + + set.add(span); + queue.add(node); + }); + }); + } + + const ends = nodes + .filter(node => node instanceof compiler.node.SpanEnd); + ends.forEach((end) => { + const active = activeMap.get(end); + assert(active.has(this.id(end)), + `Unmatched span end for "${this.id(end).name}"`); + }); + + return { active: activeMap, spans: Array.from(spans) }; + } + + private computeOverlap(info: IActiveResult): OverlapMap { + const active = info.active; + const overlap: OverlapMap = new Map(); + + info.spans.forEach(span => overlap.set(span, new Set())); + + const add = (one, list) => { + const set = overlap.get(one); + list.forEach((other) => { + if (other === one) + return; + set.add(other); + }); + }; + + active.forEach((spans) => { + spans.forEach(span => add(span, spans)); + }); + + return overlap; + } + + private color(spans: ReadonlyArray, overlapMap: OverlapMap) + : IColoringResult { + let max = -1; + const colors = new Map(); + + const allocate = (span) => { + if (colors.has(span)) + return colors.get(span); + + const overlap = overlapMap.get(span); + + // See which colors are already used + const used = new Set(); + overlap.forEach((span) => { + if (colors.has(span)) + used.add(colors.get(span)); + }); + + // Find minimum available color + let i; + for (i = 0; i < max + 1; i++) + if (!used.has(i)) + break; + + max = Math.max(max, i); + colors.set(span, i); + + return i; + }; + + const res: Map = new Map(); + + spans.forEach(span => res.set(span, allocate(span))); + + const concurrency = new Array(max + 1); + for (let i = 0; i < concurrency.length; i++) + concurrency[i] = []; + + spans.forEach(span => concurrency[allocate(span)].push(span)); + + return { map: res, concurrency, max }; + } +} diff --git a/src/llparse/compiler/stage/span-builder.ts b/src/llparse/compiler/stage/span-builder.ts new file mode 100644 index 0000000..5b9a9fb --- /dev/null +++ b/src/llparse/compiler/stage/span-builder.ts @@ -0,0 +1,230 @@ +import * as assert from 'assert'; + +import { Code } from '../code'; +import { + INT, TYPE_INPUT, TYPE_OUTPUT, TYPE_ERROR, + SPAN_START_PREFIX, SPAN_CB_PREFIX +} from '../../constants'; +import { INodePosition, Compilation, Func, BasicBlock } from '../compilation'; +import { Node } from '../node'; +import { Stage } from './base'; + +class Builder extends Stage { + constructor(ctx: Compilation) { + super(ctx, 'span-builder'); + } + + public build(): any { + this.buildFields(); + + return { + spanStart: (fn, body, code) => this.buildSpanStart(fn, body, code), + spanEnd: (fn, body, code) => this.buildSpanEnd(fn, body, code), + + preExecute: (fn, body) => this.buildPrologue(fn, body), + postExecute: (fn, body) => this.buildEpilogue(fn, body) + }; + } + + private buildFields(): void { + const colors = this.ctx.stageResults['span-allocator']; + + const callbackType = this.ctx.signature.callback.span.ptr(); + + colors.concurrency.forEach((list, index) => { + const num = list.length; + this.ctx.declareField(TYPE_INPUT, SPAN_START_PREFIX + index, + type => type.val(null)); + + if (num === 1) + return; + + this.ctx.declareField(callbackType, SPAN_CB_PREFIX + index, + type => type.val(null)); + }); + } + + // Nodes + + private buildSpanStart(fn: Func, body: BasicBlock, code: Code): BasicBlock { + const colors = this.ctx.stageResults['span-allocator']; + const index = colors.map.get(code); + + const startPtr = this.startField(fn, body, index); + + body.store(this.ctx.posArg(fn), startPtr); + + const num = colors.concurrency[index].length; + if (num !== 1) { + const cbPtr = this.callbackField(fn, body, index); + body.store(this.ctx.buildCode(code), cbPtr); + } + + return body; + } + + private buildSpanEnd(fn: Func, body: BasicBlock, code: Code): BasicBlock { + const colors = this.ctx.stageResults['span-allocator']; + const index = colors.map.get(code); + + const startPtr = this.startField(fn, body, index); + + // Load + const start = body.load(startPtr); + + // ...and reset + body.store(TYPE_INPUT.val(null), startPtr); + + const signature = this.ctx.signature.callback.span; + const pos = this.ctx.posArg(fn); + + // TODO(indutny): this won't work with internal callbacks due to + // cconv mismatch + const call = body.call(this.ctx.buildCode(code), [ + this.ctx.stateArg(fn), + start, + pos + ]); + + // Check return value + const errorCmp = body.icmp('eq', call, signature.returnType.val(0)); + + const errorBranch = this.ctx.branch(body, errorCmp, + [ 'likely', 'unlikely' ]); + const noError = errorBranch.left; + const error = errorBranch.right; + noError.name = 'no_error_' + index; + error.name = 'error_' + index; + + // Handle error + assert(TYPE_ERROR.isEqual(signature.returnType)); + this.buildError(fn, error, pos, call); + + return { + body: noError, + updateResumptionTarget: (target) => { + const currentPtr = this.ctx.stateField(fn, error, '_current'); + + error.store(target, currentPtr); + error.ret(TYPE_OUTPUT.val(null)); + } + }; + } + + private buildError(fn: Func, body: BasicBlock, pos: INodePositon, + code: Code): void { + const reason = this.ctx.cstring('Span callback error'); + + const cast = body.getelementptr(reason, INT.val(0), INT.val(0), true); + const codePtr = this.ctx.stateField(fn, body, 'error'); + const reasonPtr = this.ctx.stateField(fn, body, 'reason'); + const errorPosPtr = this.ctx.stateField(fn, body, 'error_pos'); + + body.store(code, codePtr); + body.store(cast, reasonPtr); + body.store(pos, errorPosPtr); + } + + // ${prefix}_execute + + private buildPrologue(fn: Func, body: BasicBlock): BasicBlock { + const colors = this.ctx.stageResults['span-allocator']; + + colors.concurrency.forEach((_, index) => { + const startPtr = this.startField(fn, body, index); + const start = body.load(startPtr); + + const cmp = body.icmp('eq', start, TYPE_INPUT.val(null)); + + const branch = this.ctx.branch(body, cmp); + const empty = branch.left; + const restart = branch.right; + empty.name = 'empty_' + index; + restart.name = 'restart_' + index; + + restart.store(this.ctx.posArg(fn), startPtr); + + restart.jmp(empty); + body = empty; + }); + + return body; + } + + private buildEpilogue(fn: Func, body: BasicBlock): BasicBlock { + const colors = this.ctx.stageResults['span-allocator']; + + const callbackType = this.ctx.signature.callback.span.ptr(); + + colors.concurrency.forEach((list, index) => { + const codes = list.map(code => this.ctx.buildCode(code)); + const num = codes.length; + + const startPtr = this.startField(fn, body, index); + const start = body.load(startPtr); + + const cmp = body.icmp('ne', start, TYPE_INPUT.val(null)); + + const branch = this.ctx.branch(body, cmp); + const present = branch.left; + const empty = branch.right; + present.name = 'present_' + index; + empty.name = 'empty_' + index; + + let cb; + if (num === 1) { + cb = codes[0]; + } else { + const cbPtr = this.callbackField(fn, present, index); + cb = present.load(cbPtr); + } + + const endPos = this.ctx.endPosArg(fn); + + // TODO(indutny): this won't work with internal callbacks due to + // cconv mismatch + const call = present.call(cb, [ + this.ctx.stateArg(fn), + start, + endPos + ], 'normal', 'ccc'); + if (num > 1) { + const callees = codes.map((code) => { + return this.ctx.ir.metadata(code); + }); + call.metadata.set('callees', this.ctx.ir.metadata(callees)); + } + + // Check return value + const errorCmp = present.icmp('eq', call, + callbackType.to.toSignature().returnType.val(0)); + + const errorBranch = this.ctx.branch(present, errorCmp, + [ 'likely', 'unlikely' ]); + const noError = errorBranch.left; + const error = errorBranch.right; + noError.name = 'no_error_' + index; + error.name = 'error_' + index; + + // Make sure that the types match + assert(fn.ty.toSignature().returnType.isEqual( + callbackType.toPointer().to.toSignature().returnType)); + this.buildError(fn, error, endPos, call); + error.ret(call); + + noError.jmp(empty); + body = empty; + }); + + return body; + } + + private startField(fn: Func, body: BasicBlock, index: number): values.Value { + return this.ctx.stateField(fn, body, SPAN_START_PREFIX + index); + } + + private callbackField(fn: Func, body: BasicBlock, index: number) + : values.Value { + return this.ctx.stateField(fn, body, SPAN_CB_PREFIX + index); + } +} diff --git a/src/llparse/constants.ts b/src/llparse/constants.ts new file mode 100644 index 0000000..34c4183 --- /dev/null +++ b/src/llparse/constants.ts @@ -0,0 +1,56 @@ +import { Attribute, Builder as IR, CallingConv, types } from 'bitcode'; + +import Type = types.Type; + +export const CCONV: CallingConv = 'fastcc'; + +export const BOOL: Type = IR.i(1); +export const INT: Type = IR.i(32); +export const TYPE_INPUT: Type = IR.i(8).ptr(); +export const TYPE_OUTPUT: Type = IR.i(8).ptr(); +export const TYPE_MATCH: Type = INT; +export const TYPE_INDEX: Type = IR.i(64); +export const TYPE_ERROR: Type = INT; +export const TYPE_REASON: Type = IR.i(8).ptr(); +export const TYPE_DATA: Type = IR.i(8).ptr(); +export const TYPE_STATUS: Type = IR.i(32); + +// TODO(indutny): although it works through zero extension, is there any +// cross-platform way to do it? +export const TYPE_INTPTR: Type = IR.i(64); + +export const ARG_STATE = 's'; +export const ARG_POS = 'p'; +export const ARG_ENDPOS = 'endp'; +export const ARG_SEQUENCE = 'seq'; +export const ARG_SEQUENCE_LEN = 'slen'; +export const ARG_MATCH = 'match'; +export const ARG_UNUSED = '_unused'; + +export const ATTR_STATE: ReadonlyArray = [ 'noalias', 'nonnull' ]; +export const ATTR_POS: ReadonlyArray = [ + 'noalias', 'nonnull', 'readonly', +]; +export const ATTR_ENDPOS: ReadonlyArray = [ + 'noalias', 'nonnull', 'readnone', +]; +export const ATTR_SEQUENCE = ATTR_POS; + +export const SEQUENCE_COMPLETE = 0; +export const SEQUENCE_PAUSE = 1; +export const SEQUENCE_MISMATCH = 2; + +// NOTE: It is important to start them with `_`, see `lib/llparse.js` (property) +export const SPAN_START_PREFIX = '_span_start'; +export const SPAN_CB_PREFIX = '_span_cb'; + +export const DEFAULT_TRANSLATOR_MIN_CHECK_SIZE = 32; +export const DEFAULT_TRANSLATOR_MAX_CHECK_WIDTH = 4; + +export const USER_TYPES: Map = new Map([ + [ 'i8', IR.i(8) ], + [ 'i16', IR.i(16) ], + [ 'i32', IR.i(32) ], + [ 'i64', IR.i(64) ], + [ 'ptr', IR.i(8).ptr() ], +]); diff --git a/src/llparse/index.ts b/src/llparse/index.ts new file mode 100644 index 0000000..bf6798a --- /dev/null +++ b/src/llparse/index.ts @@ -0,0 +1,11 @@ +import * as cases from './cases'; +import * as code from './code'; +import * as compiler from './compiler'; +import * as constants from './constants'; +import * as node from './node'; +import * as transform from './transform'; +import * as utils from './utils'; + +export { cases, code, compiler, constants, node, transform, utils }; +export { Span } from './span'; +export { Trie } from './trie'; diff --git a/src/llparse/node/base.ts b/src/llparse/node/base.ts new file mode 100644 index 0000000..125ceee --- /dev/null +++ b/src/llparse/node/base.ts @@ -0,0 +1,47 @@ +import * as assert from 'assert'; + +import * as cases from '../cases'; +import * as code from '../code'; +import * as node from './'; + +export abstract class Node { + private privOtherwise: cases.Otherwise | undefinded; + + constructor(public readonly name: string, + public readonly signature: code.Signature) { + } + + public otherwise(next: Node): this { + this.checkIsMatch(next, '.otherwise()'); + + assert.strictEqual(this.privOtherwise, undefined, + 'Duplicate `.otherwise()`/`.skipTo()`'); + this.privOtherwise = new cases.Otherwise(next); + + return this; + } + + public skipTo(next: Node): this { + this.checkIsMatch(next, '.skipTo()'); + + assert.strictEqual(this.privOtherwise, undefined, + 'Duplicate `.skipTo()`/`.otherwise()`'); + this.privOtherwise = new cases.Otherwise(next, true); + + return this; + } + + // Internal API below + + public getOtherwise(): cases.Otherwise | undefined { + return this.privOtherwise; + } + + protected checkIsMatch(next: Node, method: string): void { + if (!(next instanceof node.Invoke)) + return; + + assert.strictEqual(next.signature, 'match', + `Invoke of "${next.name}" can't be a target of \`${method}\``); + } +} diff --git a/src/llparse/node/consume.ts b/src/llparse/node/consume.ts new file mode 100644 index 0000000..b62a0ee --- /dev/null +++ b/src/llparse/node/consume.ts @@ -0,0 +1,10 @@ +import * as assert from 'assert'; +import { Node } from './node'; + +export class Consume extends Node { + constructor(public readonly fieldName: string) { + super('consume_' + fieldName, 'match'); + + assert(!/^_/.test(fieldName), 'Can\'t use internal field name in Consume'); + } +} diff --git a/src/llparse/node/error.ts b/src/llparse/node/error.ts new file mode 100644 index 0000000..acc930a --- /dev/null +++ b/src/llparse/node/error.ts @@ -0,0 +1,15 @@ +import * as assert from 'assert'; + +import { Node } from './node'; + +export class Error extends Node { + constructor(public readonly code: number, public readonly reason: string) { + super('error', 'match'); + + assert.strictEqual(code, code | 0, + 'The first argument of `.error()` must be an integer error code'); + } + + public otherwise(): this { throw new Error('Not supported'); } + public skipTo(): this { throw new Error('Not supported'); } +} diff --git a/src/llparse/node/index.ts b/src/llparse/node/index.ts new file mode 100644 index 0000000..e356902 --- /dev/null +++ b/src/llparse/node/index.ts @@ -0,0 +1,11 @@ +'use strict'; + +export { Node } from './base'; + +export { Match } from './match'; +export { Error } from './error'; +export { Invoke } from './invoke'; +export { SpanStart } from './span-start'; +export { SpanEnd } from './span-end'; +export { Consume } from './consume'; +export { Pause } from './pause'; diff --git a/src/llparse/node/invoke.ts b/src/llparse/node/invoke.ts new file mode 100644 index 0000000..f5b06df --- /dev/null +++ b/src/llparse/node/invoke.ts @@ -0,0 +1,25 @@ +import * as assert from 'assert'; + +import { Code } from '../code'; +import { Node } from './node'; + +export class Invoke extends Node { + public readonly map: ReadonlyMap; + + constructor(public readonly code: Code, + map: { [key: number]: Node }, otherwise?: Node) { + super('invoke_' + code.name, code.signature); + + const storedMap: Map = new Map(); + Object.keys(map).forEach((key) => { + assert.strictEqual(key, key | 0, + 'Only integer keys are allowed in `.invoke()`\'s map'); + + storedMap.set(key, map[key]!); + }); + this.map = storedMap; + + if (otherwise !== undefined) + this.otherwise(otherwise); + } +} diff --git a/src/llparse/node/match.ts b/src/llparse/node/match.ts new file mode 100644 index 0000000..bfc4ad1 --- /dev/null +++ b/src/llparse/node/match.ts @@ -0,0 +1,91 @@ +import * as assert from 'assert'; + +import { Case, Match, Peek, Select } from '../cases'; +import { Transform } from '../transform'; +import { Invoke } from './invoke'; +import { Node } from './node'; + +export class Match extends Node { + private privTransform: Transform | undefined; + private privCases: Case[] = []; + + constructor(name: string) { + super(name, 'match'); + } + + public getTransform(): Transform | undefined { + return this.privTransform; + } + + public get cases(): ReadonlyArray { + return this.privCases; + } + + public transform(t: Transform): this { + assert.strictEqual(this.privTransform, undefined, + 'Can\'t apply transform twice'); + + this.privTransform = t; + return this; + } + + public peek(value: string | ReadonlyArray, next: Node): this { + // .peek([ ... ], next) + if (Array.isArray(value)) { + value.forEach(value => this.peek(value, next)); + return this; + } + + this.checkIsMatch(next, '.peek()'); + + this.privCases.push(new Peek(value, next)); + + return this; + } + + public match(value: string | ReadonlyArray, next: Node): this { + // .match([ ... ], next) + if (Array.isArray(value)) { + value.forEach(value => this.match(value, next)); + return this; + } + + this.checkIsMatch(next, '.match()'); + + this.privCases.push(new Match(value, next)); + + return this; + } + + public select(key: Buffer | number | string | { [key: string] : number }, + value: Invoke | number, next?: Invoke): this { + // .select(key, value, next) + const pairs: { key: string, value: number }[] = []; + + if (Buffer.isBuffer(key) || typeof key === 'number' || + typeof key === 'string') { + assert.strictEqual(typeof value, 'number', + '`.select(key, value, next)` is the signature of the method'); + + pairs.push({ key, value }); + } else { + assert(value instanceof Invoke, + '`.select(map, next)` is the signature of the method'); + + const map = key; + next = value as Invoke; + + Object.keys(map).forEach((key) => pairs.push({ key, value: map[key]! })); + } + + assert.strictEqual(next.signature, 'value', + `Invoke of "${next.name}" can't be a target of \`.select()\``); + + const select = new Select(next); + pairs.forEach(pair => select.add(pair.key, pair.value)); + + this.privCases.push(select); + + return this; + } +} diff --git a/src/llparse/node/pause.ts b/src/llparse/node/pause.ts new file mode 100644 index 0000000..f2edbea --- /dev/null +++ b/src/llparse/node/pause.ts @@ -0,0 +1,16 @@ +import * as assert from 'assert'; + +import { Node } from './node'; + +export class Pause extends Node { + constructor(public readonly code: number, public readonly reason: string) { + super('pause', 'match'); + + assert.strictEqual(code, code | 0, + 'The first argument of `.error()` must be an integer error code'); + } + + public skipTo(): this { + throw new Error('Not supported, please use `pause.otherwise()`'); + } +} diff --git a/src/llparse/node/span-end.ts b/src/llparse/node/span-end.ts new file mode 100644 index 0000000..7d3fc33 --- /dev/null +++ b/src/llparse/node/span-end.ts @@ -0,0 +1,8 @@ +import { Code } from '../code'; +import { Node } from './node'; + +export class SpanEnd extends Node { + constructor(public readonly code: Code) { + super('span_end_' + code.name, 'match'); + } +} diff --git a/src/llparse/node/span-start.ts b/src/llparse/node/span-start.ts new file mode 100644 index 0000000..27eb25f --- /dev/null +++ b/src/llparse/node/span-start.ts @@ -0,0 +1,8 @@ +import { Code } from '../code'; +import { Node } from './node'; + +export class SpanStart extends Node { + constructor(public readonly code: Code) { + super('span_start_' + code.name, 'match'); + } +} diff --git a/src/llparse/span.ts b/src/llparse/span.ts new file mode 100644 index 0000000..a555266 --- /dev/null +++ b/src/llparse/span.ts @@ -0,0 +1,38 @@ +import * as assert from 'assert'; + +import { Code } from './code'; +import { Node, SpanStart, SpanEnd } from './node'; + +export class Span { + private readonly startCache: Map = new Map(); + private readonly endCache: Map = new Map(); + + constructor(public readonly code: Code) { + } + + public start(otherwise?: Node): SpanStart { + if (otherwise !== undefined && this.startCache.has(otherwise)) { + return this.startCache.get(otherwise)!; + } + + const res = new SpanStart(this.code); + if (otherwise !== undefined) { + res.otherwise(otherwise); + this.startCache.set(otherwise, res); + } + return res; + } + + public end(otherwise?: Node): SpanEnd { + if (otherwise !== undefined && this.endCache.has(otherwise)) { + return this.endCache.get(otherwise)!; + } + + const res = new SpanEnd(this[kCode]); + if (otherwise !== undefined) { + res.otherwise(otherwise); + cache.set(otherwise, res); + } + return res; + } +} diff --git a/src/llparse/transform/base.ts b/src/llparse/transform/base.ts new file mode 100644 index 0000000..84cb444 --- /dev/null +++ b/src/llparse/transform/base.ts @@ -0,0 +1,4 @@ +export class Transform { + constructor(public readonly name: string) { + } +} diff --git a/src/llparse/transform/index.ts b/src/llparse/transform/index.ts new file mode 100644 index 0000000..7342638 --- /dev/null +++ b/src/llparse/transform/index.ts @@ -0,0 +1 @@ +export { Transform } from './base'; diff --git a/src/llparse/trie/index.ts b/src/llparse/trie/index.ts new file mode 100644 index 0000000..e6bdda3 --- /dev/null +++ b/src/llparse/trie/index.ts @@ -0,0 +1,5 @@ +export { TrieNext } from './next'; +export { TrieNode } from './node'; +export { TrieSequence } from './sequence'; +export { TrieSingle } from './single'; +export { Trie } from './trie'; diff --git a/src/llparse/trie/next.ts b/src/llparse/trie/next.ts new file mode 100644 index 0000000..08bfc69 --- /dev/null +++ b/src/llparse/trie/next.ts @@ -0,0 +1,9 @@ +import { Node } from '../node'; +import { TrieNode } from './node'; + +export class TrieNext extends TrieNode { + constructor(value: number | undefined, public readonly next: Node) { + super('sequence'); + this.value = value; + } +} diff --git a/src/llparse/trie/node.ts b/src/llparse/trie/node.ts new file mode 100644 index 0000000..1fa8190 --- /dev/null +++ b/src/llparse/trie/node.ts @@ -0,0 +1,8 @@ +export type NodeType = 'single' | 'sequence' | 'next'; + +export class TrieNode { + public value: number | undefined; + + constructor(public readonly type: NodeType) { + } +} diff --git a/src/llparse/trie/sequence.ts b/src/llparse/trie/sequence.ts new file mode 100644 index 0000000..834b6d7 --- /dev/null +++ b/src/llparse/trie/sequence.ts @@ -0,0 +1,10 @@ +import { Buffer } from 'buffer'; +import { TrieNode } from './node'; + +export class TrieSequence extends TrieNode { + public child: TrieNode | undefined; + + constructor(public readonly select: Buffer) { + super('sequence'); + } +} diff --git a/src/llparse/trie/single.ts b/src/llparse/trie/single.ts new file mode 100644 index 0000000..ac12c62 --- /dev/null +++ b/src/llparse/trie/single.ts @@ -0,0 +1,15 @@ +import { TrieNode } from './node'; + +export interface ITrieSingleChild { + child: TrieNode; + key: number; + noAdvance: boolean; +} + +export class TrieSingle extends TrieNode { + public readonly children: ITrieSingleChild[] = []; + + constructor() { + super('single'); + } +} diff --git a/src/llparse/trie/trie.ts b/src/llparse/trie/trie.ts new file mode 100644 index 0000000..1676207 --- /dev/null +++ b/src/llparse/trie/trie.ts @@ -0,0 +1,122 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import { Case, ICaseLinearizeResult } from '../cases'; +import { TrieNext } from './next'; +import { TrieNode } from './node'; +import { TrieSequence } from './sequence'; +import { TrieSingle } from './single'; + +type LinearizeList = ReadonlyArray; +type Path = ReadonlyArray; + +export class Trie { + constructor(private readonly name: string) { + this.name = name; + } + + public combine(cases: ReadonlyArray): TrieNode | undefined { + const list: ICaseLinearizeResult[] = []; + cases.forEach((one) => { + one.linearize().forEach(item => list.push(item)); + }); + + if (list.length === 0) { + return undefined; + } + + list.sort((a, b) => { + return a.key.compare(b.key); + }); + + return this.level(list, []); + } + + private level(list: LinearizeList, path: Path): TrieNode { + // TODO(indutny): validate non-empty keys + const first = list[0].key; + const last = list[list.length - 1].key; + + let common = 0; + const min = Math.min(first.length, last.length); + + // Leaf + if (min === 0) { + assert.strictEqual(list.length, 1, + `Duplicate entries in "${this.name}" at [ ${path.join(', ')} ]`); + return new TrieNext(list[0].value, list[0].next); + } + + // Find the longest common sub-string + for (; common < min; common++) { + if (first[common] !== last[common]) { + break; + } + } + + // Sequence + if (common > 1) { + return this.sequence(list, first.slice(0, common), path); + } + + // Single + return this.single(list, path); + } + + private slice(list: LinearizeList, off: number): LinearizeList { + return list.map((item) => { + return { + key: item.key.slice(off), + next: item.next, + value: item.value, + noAdvance: item.noAdvance + }; + }); + } + + private sequence(list: LinearizeList, prefix: Buffer, path: Path) + : TrieSequence { + const res = new TrieSequence(prefix); + + const sliced = this.slice(list, prefix.length); + const noAdvance = sliced.some(item => item.noAdvance); + assert(!noAdvance); + res.child = this.level(sliced, path.concat(prefix)); + + return res; + } + + private single(list: LinearizeList, path: Path): TrieSingle { + const keys: Map = new Map(); + for (let i = 0; i < list.length; i++) { + const item = list[i]; + const key = item.key[0]; + + if (keys.has(key)) + keys.get(key)!.push(item); + else + keys.set(key, [ item ]); + } + + const res = new TrieSingle(); + keys.forEach((sublist, key) => { + const sliced = this.slice(sublist, 1); + const subpath = path.concat(key); + + const noAdvance = sublist.some(item => item.noAdvance); + assert( + sublist.every(item => item.noAdvance === noAdvance) || + sublist.length === 0, + 'Conflicting `.peek()` and `.match()` entries in ' + + `"${this.name}" at [ ${subpath.join(', ')} ]`); + + res.children.push({ + key, + noAdvance, + child: this.level(sliced, subpath) + }); + }); + + return res; + } +} diff --git a/src/llparse/utils.ts b/src/llparse/utils.ts new file mode 100644 index 0000000..d1b46d3 --- /dev/null +++ b/src/llparse/utils.ts @@ -0,0 +1,75 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import { Node } from './node'; + +export function toBuffer(value: number | string): Buffer { + if (typeof value === 'number') { + assert(0 <= value && value <= 255, + 'Invalid char value, must be between 0 and 255'); + assert.strictEqual(value, value | 0, 'Invalid char value, must be integer'); + return Buffer.from([ value ]); + } else { + return Buffer.from(value); + } +} + +export function powerOfTwo(num: number): number { + return Math.pow(2, Math.ceil(Math.log2(num))); +} + +export interface ILookupListEntry { + node: Node; + noAdvance: boolean; + keys: ReadonlyArray; +} + +export interface ILookupResult { + indexShift: number; + shiftMask: number; + shiftMul: number; + table: ReadonlyArray; + valueMask: number; +} + +export function buildLookupTable(wordWidth: number, charWidth: number, + list: ReadonlyArray) + : ILookupResult { + // Entry values: + // 0 - no hit + // 1 - map[0] + // ... + // n - map[n] + const width = exports.powerOfTwo(Math.ceil(Math.log2(list.length + 1))); + assert(width >= 1); + + assert(charWidth + width - wordWidth > 0); + + // TODO(indutny): 64-bit table through BN support? + const indexShift = wordWidth - Math.log2(width); + const table = new Array(1 << (charWidth - indexShift)).fill(0); + + assert(indexShift > 0); + + const shiftMask = (1 << indexShift) - 1; + const shiftMul = width; + + list.forEach((entry, i) => { + const val = i + 1; + + entry.keys.forEach((key) => { + const index = key >> indexShift; + const shift = (key & shiftMask) * shiftMul; + + table[index] |= val << shift; + }); + }); + + return { + table, + indexShift, + shiftMask, + shiftMul, + valueMask: (1 << width) - 1 + }; +} diff --git a/test/api-test.js b/test/api-test.js new file mode 100644 index 0000000..15d1f8f --- /dev/null +++ b/test/api-test.js @@ -0,0 +1,257 @@ +'use strict'; +/* global describe it beforeEach */ + +const llparse = require('../'); + +const fixtures = require('./fixtures'); + +const printOff = fixtures.printOff; +const printMatch = fixtures.printMatch; + +describe('LLParse', function() { + this.timeout(fixtures.TIMEOUT); + + let p; + beforeEach(() => { + p = llparse.create('llparse'); + }); + + it('should compile simple parser', (callback) => { + const start = p.node('start'); + + start.match(' ', start); + + start.match('HTTP', printOff(p, start)); + + start.select({ + 'HEAD': 0, 'GET': 1, 'POST': 2, 'PUT': 3, + 'DELETE': 4, 'OPTIONS': 5, 'CONNECT': 6, + 'TRACE': 7, 'PATCH': 8 + }, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = fixtures.build(p, start, 'simple'); + + binary('GET', 'off=3 match=1\n', callback); + }); + + it('should optimize shallow select', (callback) => { + const start = p.node('start'); + + start.select({ + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, + '5': 5, '6': 6, '7': 7, '8': 8, '9': 9 + }, printMatch(p, start)); + + start.otherwise(p.error(3, 'Invalid word')); + + const binary = fixtures.build(p, start, 'shallow'); + + binary('012', 'off=1 match=0\noff=2 match=1\noff=3 match=2\n', callback); + }); + + it('should support key-value select', (callback) => { + 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 = fixtures.build(p, start, 'kv-select'); + + binary('012', 'off=1 match=0\noff=2 match=1\noff=3 match=2\n', callback); + }); + + it('should support multi-match', (callback) => { + 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 = fixtures.build(p, start, 'multi-match'); + binary( + 'A B\t\tA\r\nA', + 'off=1 match=0\noff=3 match=1\noff=6 match=0\noff=9 match=0\n', + callback); + }); + + it('should support numeric-match', (callback) => { + 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 = fixtures.build(p, start, 'multi-match'); + binary( + 'A B A A', + 'off=1 match=0\noff=3 match=1\noff=6 match=0\noff=9 match=0\n', + callback); + }); + + it('should support custom state properties', (callback) => { + 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 = fixtures.build(p, start, 'custom-prop'); + + binary('0110', 'off=1 0\noff=2 1\noff=3 1\noff=4 0\n', callback); + }); + + it('should return error code/reason', (callback) => { + const start = p.node('start'); + + start.match('a', start); + start.otherwise(p.error(42, 'some reason')); + + const binary = fixtures.build(p, start, 'error'); + + binary('aab', 'off=2 error code=42 reason="some reason"\n', callback); + }); + + it('should not merge `.match()` with `.peek()`', (callback) => { + 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 = fixtures.build(p, maybeCr, 'no-merge'); + + binary('\r\n\n', 'off=2\noff=3\n', callback); + }); + + describe('`.match()`', () => { + it('should compile to a single-bit bit-check node', (callback) => { + const start = p.node('start'); + + start + .match(fixtures.ALPHA, start) + .skipTo(printOff(p, start)); + + // TODO(indutny): validate compilation result? + const binary = fixtures.build(p, start, 'match-bit-check'); + + binary('pecan.is.dead.', 'off=6\noff=9\noff=14\n', callback); + }); + + it('should compile to a multi-bit bit-check node', (callback) => { + const start = p.node('start'); + const another = p.node('another'); + + start + .match(fixtures.ALPHA, start) + .peek(fixtures.NUM, another) + .skipTo(printOff(p, start)); + + another + .match(fixtures.NUM, another) + .otherwise(start); + + // TODO(indutny): validate compilation result? + const binary = fixtures.build(p, start, 'match-multi-bit-check'); + + binary('pecan.135.is.dead.', 'off=6\noff=10\noff=13\noff=18\n', callback); + }); + }); + + describe('`.peek()`', () => { + it('should not advance position', (callback) => { + 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 = fixtures.build(p, start, 'peek'); + + binary('ab', 'off=1\noff=2\n', callback); + }); + }); + + describe('`.otherwise()`', () => { + it('should not advance position by default', (callback) => { + const p = llparse.create('llparse'); + + 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 = fixtures.build(p, a, 'otherwise-noadvance'); + + binary('AABAB', 'off=3\noff=5\n', callback); + }); + + it('should advance when it is `.skipTo()`', (callback) => { + const p = llparse.create('llparse'); + + const start = p.node('start'); + + start + .match(' ', printOff(p, start)) + .skipTo(start); + + const binary = fixtures.build(p, start, 'otherwise-skip'); + + binary('HELLO WORLD', 'off=6\n', callback); + }); + + it('should skip everything with `.skipTo()`', (callback) => { + const p = llparse.create('llparse'); + + const start = p.node('start'); + + start + .skipTo(start); + + const binary = fixtures.build(p, start, 'all-skip'); + + binary('HELLO WORLD', '\n', callback); + }); + }); +}); diff --git a/test/code-test.ts b/test/code-test.js similarity index 59% rename from test/code-test.ts rename to test/code-test.js index 7f74b9e..b9cb127 100644 --- a/test/code-test.ts +++ b/test/code-test.js @@ -1,79 +1,88 @@ -import { LLParse } from '../src/api'; +'use strict'; +/* global describe it beforeEach */ -import { build, NUM_SELECT, printOff } from './fixtures'; +const llparse = require('../'); -describe('llparse/code', () => { - let p: LLParse; +const fixtures = require('./fixtures'); +const printOff = fixtures.printOff; + +describe('LLParse/Code', function() { + this.timeout(fixtures.TIMEOUT); + + let p; beforeEach(() => { - p = new LLParse(); + p = llparse.create('llparse'); }); describe('`.mulAdd()`', () => { - it('should operate normally', async () => { + it('should operate normally', (callback) => { 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)), + 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) + .select(fixtures.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'); + const binary = fixtures.build(p, start, 'mul-add'); + + binary('1337.', 'off=5\n', callback); }); - it('should operate fail on overflow', async () => { + it('should operate fail on overflow', (callback) => { const start = p.node('start'); p.property('i8', 'counter'); const count = p.invoke(p.code.mulAdd('counter', { base: 10 }), { - 1: printOff(p, start), + 1: printOff(p, start) }, start); start - .select(NUM_SELECT, count) + .select(fixtures.NUM_SELECT, count) .otherwise(p.error(1, 'Unexpected')); - const binary = await build(p, start, 'mul-add-overflow'); - await binary.check('1111', 'off=4\n'); + const binary = fixtures.build(p, start, 'mul-add-overflow'); + + binary('1111', 'off=4\n', callback); }); - it('should operate fail on greater than max', async () => { + it('should operate fail on greater than max', (callback) => { const start = p.node('start'); p.property('i64', 'counter'); const count = p.invoke(p.code.mulAdd('counter', { base: 10, - max: 1000, + max: 1000 }), { - 1: printOff(p, start), + 1: printOff(p, start) }, start); start - .select(NUM_SELECT, count) + .select(fixtures.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'); + const binary = fixtures.build(p, start, 'mul-add-max-overflow'); + + binary('1111', 'off=4\n', callback); }); }); describe('`.update()`', () => { - it('should operate normally', async () => { + it('should operate normally', (callback) => { const start = p.node('start'); p.property('i64', 'counter'); @@ -85,36 +94,38 @@ describe('llparse/code', () => { update .otherwise(p.invoke(p.code.load('counter'), { - 42: printOff(p, start), + 42: printOff(p, start) }, p.error(1, 'Unexpected'))); - const binary = await build(p, start, 'update'); - await binary.check('.', 'off=1\n'); + const binary = fixtures.build(p, start, 'update'); + + binary('.', 'off=1\n', callback); }); }); describe('`.isEqual()`', () => { - it('should operate normally', async () => { + it('should operate normally', (callback) => { const start = p.node('start'); p.property('i64', 'counter'); const check = p.invoke(p.code.isEqual('counter', 1), { - 0: printOff(p, start), - 1: start, + 0: fixtures.printOff(p, start), + 1: start }, p.error(1, 'Unexpected')); start - .select(NUM_SELECT, p.invoke(p.code.store('counter'), check)) + .select(fixtures.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'); + const binary = fixtures.build(p, start, 'is-equal'); + + binary('010', 'off=1\noff=3\n', callback); }); }); - describe('`.or()`/`.and()`/`.test()`', () => { - it('should set and retrieve bits', async () => { + describe('`.or()`/`.test()`', () => { + it('should set and retrieve bits', (callback) => { const start = p.node('start'); const test = p.node('test'); @@ -126,8 +137,6 @@ describe('llparse/code', () => { .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')); @@ -135,32 +144,32 @@ describe('llparse/code', () => { test .match('1', p.invoke(p.code.test('flag', 1), { 0: test, - 1: printOff(p, 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), + 1: printOff(p, test) }, p.error(3, 'test-2'))) .match('4', p.invoke(p.code.test('flag', 4), { 0: test, - 1: printOff(p, 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), + 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', [ + const binary = fixtures.build(p, start, 'or-test'); + + binary('1-124.2-1247.4-1247.r4-124.', [ 'off=3', 'off=9', 'off=10', 'off=16', 'off=17', 'off=18', 'off=19', - 'off=26', - 'off=34', - ]); + 'off=26' + ], callback); }); }); }); diff --git a/test/compiler-test.ts b/test/compiler-test.ts deleted file mode 100644 index 39bb69f..0000000 --- a/test/compiler-test.ts +++ /dev/null @@ -1,289 +0,0 @@ -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/test/consume-test.js b/test/consume-test.js new file mode 100644 index 0000000..88e04ff --- /dev/null +++ b/test/consume-test.js @@ -0,0 +1,59 @@ +'use strict'; +/* global describe it beforeEach */ + +const llparse = require('../'); + +const fixtures = require('./fixtures'); + +const printOff = fixtures.printOff; + +describe('LLParse/consume', function() { + this.timeout(fixtures.TIMEOUT); + + let p; + beforeEach(() => { + p = llparse.create('llparse'); + }); + + it('should consume bytes with i8 field', (callback) => { + p.property('i8', 'to_consume'); + + const start = p.node('start'); + const consume = p.consume('to_consume'); + + start.select({ + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4 + }, p.invoke(p.code.store('to_consume'), consume)); + + start + .otherwise(p.error(1, 'unexpected')); + + consume + .otherwise(printOff(p, start)); + + const binary = fixtures.build(p, start, 'consume'); + + binary('3aaa2bb1a01b', 'off=4\noff=7\noff=9\noff=10\noff=12\n', callback); + }); + + it('should consume bytes with i64 field', (callback) => { + p.property('i64', 'to_consume'); + + const start = p.node('start'); + const consume = p.consume('to_consume'); + + start.select({ + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4 + }, p.invoke(p.code.store('to_consume'), consume)); + + start + .otherwise(p.error(1, 'unexpected')); + + consume + .otherwise(printOff(p, start)); + + const binary = fixtures.build(p, start, 'consume'); + + binary('3aaa2bb1a01b', 'off=4\noff=7\noff=9\noff=10\noff=12\n', callback); + }); +}); diff --git a/test/consume-test.ts b/test/consume-test.ts deleted file mode 100644 index 8cf305b..0000000 --- a/test/consume-test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { LLParse } from '../src/api'; - -import { build, NUM_SELECT, 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/test/fixtures/extra.c b/test/fixtures/extra.c index d9179b6..3a3f9d6 100644 --- a/test/fixtures/extra.c +++ b/test/fixtures/extra.c @@ -1,6 +1,6 @@ #include "fixture.h" -int llparse__print_zero(llparse_t* s, const char* p, const char* endp) { +int llparse__print_zero(llparse_state_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; llparse__print(p, endp, "0"); @@ -8,7 +8,7 @@ int llparse__print_zero(llparse_t* s, const char* p, const char* endp) { } -int llparse__print_one(llparse_t* s, const char* p, const char* endp) { +int llparse__print_one(llparse_state_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; llparse__print(p, endp, "1"); @@ -16,7 +16,7 @@ int llparse__print_one(llparse_t* s, const char* p, const char* endp) { } -int llparse__print_off(llparse_t* s, const char* p, const char* endp) { +int llparse__print_off(llparse_state_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; llparse__print(p, endp, ""); @@ -24,7 +24,7 @@ int llparse__print_off(llparse_t* s, const char* p, const char* endp) { } -int llparse__print_match(llparse_t* s, const char* p, const char* endp, +int llparse__print_match(llparse_state_t* s, const char* p, const char* endp, int value) { if (llparse__in_bench) return 0; @@ -33,21 +33,21 @@ int llparse__print_match(llparse_t* s, const char* p, const char* endp, } -int llparse__on_dot(llparse_t* s, const char* p, const char* endp) { +int llparse__on_dot(llparse_state_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) { +int llparse__on_dash(llparse_state_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, +int llparse__on_underscore(llparse_state_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; @@ -56,8 +56,7 @@ int llparse__on_underscore(llparse_t* s, const char* p, /* A span callback, really */ -int llparse__please_fail(llparse_t* s, const char* p, const char* endp) { - s->reason = "please fail"; +int llparse__please_fail(llparse_state_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 1; return 1; @@ -67,7 +66,7 @@ int llparse__please_fail(llparse_t* s, const char* p, const char* endp) { /* A span callback, really */ static int llparse__pause_once_counter; -int llparse__pause_once(llparse_t* s, const char* p, const char* endp) { +int llparse__pause_once(llparse_state_t* s, const char* p, const char* endp) { if (!llparse__in_bench) llparse__print_span("pause", p, endp); @@ -77,8 +76,3 @@ int llparse__pause_once(llparse_t* s, const char* p, const char* endp) { return LLPARSE__ERROR_PAUSE; } - - -void llparse__test_init(llparse_t* p) { - llparse__pause_once_counter = 0; -} diff --git a/test/fixtures/index.js b/test/fixtures/index.js new file mode 100644 index 0000000..011c37f --- /dev/null +++ b/test/fixtures/index.js @@ -0,0 +1,42 @@ +'use strict'; + +const path = require('path'); + +const Fixture = require('llparse-test-fixture'); + +const fixtures = Fixture.create({ + buildDir: path.join(__dirname, '..', 'tmp'), + extra: [ path.join(__dirname, 'extra.c') ] +}); + +exports.build = (...args) => fixtures.build(...args); + +exports.printMatch = (p, next) => { + const code = p.code.value('llparse__print_match'); + + return p.invoke(code, next); +}; + +exports.printOff = (p, next) => { + const code = p.code.match('llparse__print_off'); + + return p.invoke(code, next); +}; + +exports.NUM_SELECT = { + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9 +}; + +exports.NUM = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; + +exports.ALPHA = [ + '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' +]; + +exports.ERROR_PAUSE = Fixture.ERROR_PAUSE; + +// Reasonable timeout for CI +exports.TIMEOUT = 10000; diff --git a/test/fixtures/index.ts b/test/fixtures/index.ts deleted file mode 100644 index f8f71fe..0000000 --- a/test/fixtures/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -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 { - 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 = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', -]; - -export const ALPHA: ReadonlyArray = [ - '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/test/loop-check-test.js b/test/loop-check-test.js new file mode 100644 index 0000000..18713a7 --- /dev/null +++ b/test/loop-check-test.js @@ -0,0 +1,68 @@ +'use strict'; +/* global describe it beforeEach */ + +const assert = require('assert'); + +const llparse = require('../'); + +const fixtures = require('./fixtures'); + +describe('LLParse/node-loop-checker', function() { + this.timeout(fixtures.TIMEOUT); + + let p; + beforeEach(() => { + p = llparse.create('llparse'); + }); + + it('should detect loops', () => { + const start = p.node('start'); + const a = p.node('a'); + const invoke = p.invoke(p.code.match('nop'), { + 0: start + }, p.error(1, 'error')); + + start + .peek('a', a) + .otherwise(p.error(1, 'error')); + + a.otherwise(invoke); + + assert.throws(() => { + p.build(start); + }, /detected in "start".*"invoke_nop"/); + }); + + it('should ignore loops through `peek` to `match`', () => { + const start = p.node('start'); + const a = p.node('a'); + const invoke = p.invoke(p.code.match('nop'), { + 0: start + }, p.error(1, 'error')); + + start + .peek('a', a) + .otherwise(p.error(1, 'error')); + + a + .match('a', invoke) + .otherwise(start); + + assert.doesNotThrow(() => p.build(start)); + }); + + it('should ignore irrelevant `peek`s', () => { + const start = p.node('start'); + const a = p.node('a'); + + start + .peek('a', a) + .otherwise(p.error(1, 'error')); + + a + .peek('b', start) + .otherwise(p.error(1, 'error')); + + assert.doesNotThrow(() => p.build(start)); + }); +}); diff --git a/test/resumption-test.js b/test/resumption-test.js new file mode 100644 index 0000000..d71c3b1 --- /dev/null +++ b/test/resumption-test.js @@ -0,0 +1,59 @@ +'use strict'; +/* global describe it beforeEach */ + +const llparse = require('../'); + +const fixtures = require('./fixtures'); + +describe('LLParse/resumption', function() { + this.timeout(fixtures.TIMEOUT); + + let p; + beforeEach(() => { + p = llparse.create('llparse'); + }); + + it('should resume after span end pause', (callback) => { + 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 = fixtures.build(p, start, 'resume-span'); + + binary('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'), + callback); + }); + + it('should resume after `pause` node', (callback) => { + const start = p.node('start'); + const pause = p.pause(fixtures.ERROR_PAUSE, 'paused'); + + start + .match('p', pause) + .skipTo(start); + + pause + .otherwise(fixtures.printOff(p, start)); + + const binary = fixtures.build(p, start, 'resume-pause'); + + binary('..p....p..', + 'off=3 pause\noff=3\noff=8 pause\noff=8\n', + callback); + }); +}); diff --git a/test/resumption-test.ts b/test/resumption-test.ts deleted file mode 100644 index 7fe9de0..0000000 --- a/test/resumption-test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { LLParse } from '../src/api'; - -import { build, ERROR_PAUSE, 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/test/span-test.js b/test/span-test.js new file mode 100644 index 0000000..4e76f2b --- /dev/null +++ b/test/span-test.js @@ -0,0 +1,108 @@ +'use strict'; +/* global describe it beforeEach */ + +const assert = require('assert'); + +const llparse = require('../'); + +const fixtures = require('./fixtures'); + +describe('LLParse/span', function() { + this.timeout(fixtures.TIMEOUT); + + let p; + beforeEach(() => { + p = llparse.create('llparse'); + }); + + it('should invoke span callback', (callback) => { + const start = p.node('start'); + const dot = p.node('dot'); + const dash = p.node('dash'); + const underscore = p.node('underscore'); + + const span = { + dot: p.span(p.code.span('llparse__on_dot')), + dash: p.span(p.code.span('llparse__on_dash')), + 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 = fixtures.build(p, start, 'span'); + + binary( + '..--..__..', + 'off=2 len=2 span[dash]="--"\n' + + 'off=6 len=2 span[underscore]="__"\n' + + 'off=0 len=10 span[dot]="..--..__.."\n', + callback); + }); + + it('should throw on loops', () => { + const start = p.node('start'); + const span = p.span(p.code.span('llparse__on_data')); + + start.otherwise(span.start().skipTo(start)); + + assert.throws(() => p.build(start), /loop.*on_data/); + }); + + it('should throw on unmatched ends', () => { + const start = p.node('start'); + const span = p.span(p.code.span('llparse__on_data')); + + start.otherwise(span.end().skipTo(start)); + + assert.throws(() => p.build(start), /unmatched.*on_data/i); + }); + + it('should propagate through the Invoke map', () => { + const start = p.node('start'); + const span = p.span(p.code.span('llparse__on_data')); + + p.property('i8', 'custom'); + + start.otherwise(p.invoke(p.code.load('custom'), { + 0: span.end().skipTo(start) + }, span.end().skipTo(start))); + + assert.doesNotThrow(() => p.build(span.start(start))); + }); + + it('should return error', (callback) => { + 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 = fixtures.build(p, start, 'span-error'); + + binary( + '....a', + /off=\d+ error code=1 reason="Span callback error"\n/, + callback); + }); +}); diff --git a/test/span-test.ts b/test/span-test.ts deleted file mode 100644 index ed0e2cd..0000000 --- a/test/span-test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { LLParse } from '../src/api'; - -import { build } 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/test/transform-test.js b/test/transform-test.js new file mode 100644 index 0000000..8cde65b --- /dev/null +++ b/test/transform-test.js @@ -0,0 +1,29 @@ +'use strict'; +/* global describe it beforeEach */ + +const llparse = require('../'); + +const fixtures = require('./fixtures'); + +describe('LLParse/transform', function() { + this.timeout(fixtures.TIMEOUT); + + let p; + beforeEach(() => { + p = llparse.create('llparse'); + }); + + it('should apply transformation before the match', (callback) => { + const start = p.node('start'); + + start + .transform(p.transform.toLowerUnsafe()) + .match('connect', fixtures.printOff(p, start)) + .match('close', fixtures.printOff(p, start)) + .otherwise(p.error(1, 'error')); + + const binary = fixtures.build(p, start, 'transform-lower'); + + binary('connectCLOSEcOnNeCt', 'off=7\noff=12\noff=19\n', callback); + }); +}); diff --git a/test/transform-test.ts b/test/transform-test.ts deleted file mode 100644 index 1ee61b3..0000000 --- a/test/transform-test.ts +++ /dev/null @@ -1,39 +0,0 @@ -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/test/utils-test.js b/test/utils-test.js new file mode 100644 index 0000000..435ae39 --- /dev/null +++ b/test/utils-test.js @@ -0,0 +1,73 @@ +'use strict'; +/* global describe it */ + +const assert = require('assert'); + +const llparse = require('../lib/llparse/'); + +describe('LLParse/utils', function() { + describe('lookup table', () => { + const enumerate = (table) => { + const res = new Map(); + + for (let c = 0; c < 256; c++) { + const index = c >>> table.indexShift; + const shift = (c & table.shiftMask) * table.shiftMul; + + const v = (table.table[index] >>> shift) & table.valueMask; + if (v === 0) + continue; + + if (res.has(v - 1)) + res.get(v - 1).push(c); + else + res.set(v - 1, [ c ]); + } + + const sorted = []; + res.forEach((values, key) => sorted.push({ key, values })); + return sorted.sort((a, b) => a.key - b.key); + }; + + it('should build correct lookup table width=1', () => { + const table = llparse.utils.buildLookupTable(5, 8, [ + [ 1, 3, 5, 101, 255 ] + ]); + + const res = enumerate(table); + assert.deepStrictEqual(res, [ + { key: 0, values: [ 1, 3, 5, 101, 255 ] } + ]); + }); + + it('should build correct lookup table width=2', () => { + const table = llparse.utils.buildLookupTable(5, 8, [ + [ 1, 3, 5, 101, 254 ], + [ 2, 4, 6, 102, 255 ] + ]); + + const res = enumerate(table); + assert.deepStrictEqual(res, [ + { key: 0, values: [ 1, 3, 5, 101, 254 ] }, + { key: 1, values: [ 2, 4, 6, 102, 255 ] } + ]); + }); + + it('should build correct lookup table width=3', () => { + const table = llparse.utils.buildLookupTable(5, 8, [ + [ 1, 5, 9, 13, 17, 117 ], + [ 2, 6, 10, 14, 18, 84 ], + [ 3, 7, 11, 15, 19, 90 ], + [ 4, 8, 12, 16, 20, 255 ] + ]); + + const res = enumerate(table); + assert.deepStrictEqual(res, [ + { key: 0, values: [ 1, 5, 9, 13, 17, 117 ] }, + { key: 1, values: [ 2, 6, 10, 14, 18, 84 ] }, + { key: 2, values: [ 3, 7, 11, 15, 19, 90 ] }, + { key: 3, values: [ 4, 8, 12, 16, 20, 255 ] } + ]); + }); + }); +}); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json deleted file mode 100644 index 915c575..0000000 --- a/tsconfig.eslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": [ - "**/*.ts" - ] - } diff --git a/tsconfig.json b/tsconfig.json index 4fa0169..4c60f42 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,12 @@ { "compilerOptions": { "strict": true, - "target": "es2022", + "target": "es2017", "module": "commonjs", - "moduleResolution": "node", "outDir": "./lib", "declaration": true, "pretty": true, - "sourceMap": true, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": true + "sourceMap": true }, "include": [ "src/**/*.ts" diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..b0aaf97 --- /dev/null +++ b/tslint.json @@ -0,0 +1,14 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-bitwise": null, + "quotemark": [ + true, "single", "avoid-escape", "avoid-template" + ] + }, + "rulesDirectory": [] +}