From 05feaa0e85dd7e6cee60ac9e74d4f60136f617b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Apr 2026 03:25:05 +0000 Subject: [PATCH] perf(parser): replace regex tokenizer with state-machine + keyword trie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces TokenizerFSM, a hand-rolled state machine that tokenizes via: - a character-keyed trie for English/Vietnamese (multi-word) keywords with per-entry boundary rules (\b for ASCII, custom for VI, none for else/return/ try/as/from/const/async — matching the original regex spec exactly); - bounded backtracking only when distinguishing multi-word identifiers from multi-word keywords (peek next word, rewind on no match); - direct char-code dispatch for whitespace/comments/numbers/operators, eliminating the per-token linear scan over ~80 regex specs and the String#slice + concat in the regex tokenizer's hot path. Wired through Parser via a `tokenizerKind: 'fsm' | 'regex'` option (default 'fsm'). The legacy Tokenizer is kept side-by-side for parity testing and rollback. Tests: 402/402 pass (regex baseline was 347; +55 new tests for fixtures, parity, snapshots, and the FSM tokenizer itself). Bench (vitest bench, side-by-side): tiny 1.8k hz → 364k hz (~206x) medium 48 hz → 26k hz (~546x) keywordHeavy 33 hz → 20k hz (~609x) stringHeavy 143 hz → 69k hz (~484x) large (14k) 1.1 hz → 3.3k hz (~2969x — eliminates super-linear regex behavior) Adds a benchmark harness (vitest bench, fixtures + tokenizer + parser benches), captured baseline JSON, and token-stream snapshots so future tokenizer changes are guarded against silent drift. https://claude.ai/code/session_01DwkDg2bBKFmfGmfP3Xtg7o --- package.json | 2 + packages/parser/bench/baseline.json | 156 + packages/parser/bench/comparison.json | 301 ++ .../parser/src/__bench__/fixtures/index.ts | 33 + .../src/__bench__/fixtures/keyword-heavy.vjs | 98 + .../parser/src/__bench__/fixtures/medium.vjs | 101 + .../src/__bench__/fixtures/string-heavy.vjs | 39 + .../parser/src/__bench__/fixtures/tiny.vjs | 10 + packages/parser/src/__bench__/parser.bench.ts | 18 + .../parser/src/__bench__/tokenizer.bench.ts | 47 + .../bench-fixture-snapshot.test.ts.snap | 4254 +++++++++++++++++ .../src/__test__/bench-fixture-parity.test.ts | 28 + .../__test__/bench-fixture-snapshot.test.ts | 28 + .../parser/src/__test__/tokenizer-fsm.test.ts | 232 + packages/parser/src/index.ts | 2 + .../src/nodes/literals/TemplateLiteral.ts | 7 +- packages/parser/src/parser.ts | 30 +- packages/parser/src/tokenizer-fsm.ts | 917 ++++ vitest.config.ts | 11 + 19 files changed, 6306 insertions(+), 8 deletions(-) create mode 100644 packages/parser/bench/baseline.json create mode 100644 packages/parser/bench/comparison.json create mode 100644 packages/parser/src/__bench__/fixtures/index.ts create mode 100644 packages/parser/src/__bench__/fixtures/keyword-heavy.vjs create mode 100644 packages/parser/src/__bench__/fixtures/medium.vjs create mode 100644 packages/parser/src/__bench__/fixtures/string-heavy.vjs create mode 100644 packages/parser/src/__bench__/fixtures/tiny.vjs create mode 100644 packages/parser/src/__bench__/parser.bench.ts create mode 100644 packages/parser/src/__bench__/tokenizer.bench.ts create mode 100644 packages/parser/src/__test__/__snapshots__/bench-fixture-snapshot.test.ts.snap create mode 100644 packages/parser/src/__test__/bench-fixture-parity.test.ts create mode 100644 packages/parser/src/__test__/bench-fixture-snapshot.test.ts create mode 100644 packages/parser/src/__test__/tokenizer-fsm.test.ts create mode 100644 packages/parser/src/tokenizer-fsm.ts diff --git a/package.json b/package.json index 79c6939..9b7cfc2 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "typecheck": "nx run-many --target=typecheck --all", "test": "vitest run", "test:coverage": "vitest run --coverage", + "bench": "vitest bench --run", + "bench:baseline": "vitest bench --run --outputJson packages/parser/bench/baseline.json", "coverage:summary": "node scripts/compat-summary.mjs", "dev": "vitest --ui", "dev:docs": "vitepress dev docs", diff --git a/packages/parser/bench/baseline.json b/packages/parser/bench/baseline.json new file mode 100644 index 0000000..bccbd19 --- /dev/null +++ b/packages/parser/bench/baseline.json @@ -0,0 +1,156 @@ +{ + "files": [ + { + "filepath": "/home/user/vietscript/packages/parser/src/__bench__/parser.bench.ts", + "groups": [ + { + "fullName": "packages/parser/src/__bench__/parser.bench.ts > parser end-to-end (regex baseline)", + "benchmarks": [ + { + "id": "1166627732_0_0", + "name": "parse tiny (129 chars)", + "rank": 1, + "rme": 0, + "samples": [] + } + ] + } + ] + }, + { + "filepath": "/home/user/vietscript/packages/parser/src/__bench__/tokenizer.bench.ts", + "groups": [ + { + "fullName": "packages/parser/src/__bench__/tokenizer.bench.ts > tokenizer (regex baseline)", + "benchmarks": [ + { + "id": "-1068277940_0_0", + "name": "tokenize tiny (129 chars)", + "rank": 1, + "rme": 1.1426219639578934, + "samples": [], + "totalTime": 500.33317099999954, + "min": 0.4448810000001231, + "max": 1.2078570000001037, + "hz": 1964.6908439736467, + "period": 0.5089859318413017, + "mean": 0.5089859318413017, + "variance": 0.008654820571585425, + "sd": 0.0930312881324634, + "sem": 0.002967237270752279, + "df": 982, + "critical": 1.96, + "moe": 0.005815785050674467, + "p75": 0.5024739999998928, + "p99": 0.8974330000000919, + "p995": 0.955120000000079, + "p999": 1.2078570000001037, + "sampleCount": 983, + "median": 0.4800920000000133 + }, + { + "id": "-1068277940_0_1", + "name": "tokenize medium (1777 chars)", + "rank": 3, + "rme": 0.6302605202939511, + "samples": [], + "totalTime": 516.6511870000006, + "min": 19.24686200000042, + "max": 20.44344299999989, + "hz": 50.324088387316465, + "period": 19.871199500000024, + "mean": 19.871199500000024, + "variance": 0.09610086164718201, + "sd": 0.3100013897504042, + "sem": 0.06079627444531513, + "df": 25, + "critical": 2.06, + "moe": 0.12524032535734916, + "p75": 20.03240300000016, + "p99": 20.44344299999989, + "p995": 20.44344299999989, + "p999": 20.44344299999989, + "sampleCount": 26, + "median": 19.872763499999905 + }, + { + "id": "-1068277940_0_2", + "name": "tokenize keywordHeavy (2511 chars)", + "rank": 4, + "rme": 0.5495901106494003, + "samples": [], + "totalTime": 506.61291200000005, + "min": 27.58257700000013, + "max": 28.69541799999979, + "hz": 35.53008534452829, + "period": 28.14516177777778, + "mean": 28.14516177777778, + "variance": 0.09673706615570357, + "sd": 0.3110258287597729, + "sem": 0.07330949088006712, + "df": 17, + "critical": 2.11, + "moe": 0.15468302575694162, + "p75": 28.271655999999894, + "p99": 28.69541799999979, + "p995": 28.69541799999979, + "p999": 28.69541799999979, + "sampleCount": 18, + "median": 28.091197499999907 + }, + { + "id": "-1068277940_0_3", + "name": "tokenize stringHeavy (1410 chars)", + "rank": 2, + "rme": 0.6673262731198819, + "samples": [], + "totalTime": 500.565410000002, + "min": 6.132623999999851, + "max": 7.134853000000021, + "hz": 149.83056859641923, + "period": 6.674205466666693, + "mean": 6.674205466666693, + "variance": 0.037456074319165325, + "sd": 0.193535718458287, + "sem": 0.02234757982993992, + "df": 74, + "critical": 1.993, + "moe": 0.044538726601070264, + "p75": 6.781645000000026, + "p99": 7.134853000000021, + "p995": 7.134853000000021, + "p999": 7.134853000000021, + "sampleCount": 75, + "median": 6.676206999999977 + }, + { + "id": "-1068277940_0_4", + "name": "tokenize large (14262 chars)", + "rank": 5, + "rme": 1.1911127448688184, + "samples": [], + "totalTime": 8981.281721999996, + "min": 888.0166659999995, + "max": 939.8688099999999, + "hz": 1.1134268258732618, + "period": 898.1281721999997, + "mean": 898.1281721999997, + "variance": 223.66456306729435, + "sd": 14.955419187281056, + "sem": 4.729318799439242, + "df": 9, + "critical": 2.262, + "moe": 10.697719124331565, + "p75": 896.0057759999981, + "p99": 939.8688099999999, + "p995": 939.8688099999999, + "p999": 939.8688099999999, + "sampleCount": 10, + "median": 893.9150835 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/parser/bench/comparison.json b/packages/parser/bench/comparison.json new file mode 100644 index 0000000..31011fc --- /dev/null +++ b/packages/parser/bench/comparison.json @@ -0,0 +1,301 @@ +{ + "files": [ + { + "filepath": "/home/user/vietscript/packages/parser/src/__bench__/parser.bench.ts", + "groups": [ + { + "fullName": "packages/parser/src/__bench__/parser.bench.ts > parser end-to-end (regex baseline)", + "benchmarks": [ + { + "id": "1166627732_0_0", + "name": "parse tiny (129 chars)", + "rank": 1, + "rme": 0, + "samples": [] + } + ] + } + ] + }, + { + "filepath": "/home/user/vietscript/packages/parser/src/__bench__/tokenizer.bench.ts", + "groups": [ + { + "fullName": "packages/parser/src/__bench__/tokenizer.bench.ts > tokenize tiny (129 chars)", + "benchmarks": [ + { + "id": "-1068277940_0_0", + "name": "regex", + "rank": 2, + "rme": 1.5350840371170378, + "samples": [], + "totalTime": 500.0193509999999, + "min": 0.4276030000000901, + "max": 1.3723669999999402, + "hz": 1769.931500111083, + "period": 0.5649936169491524, + "mean": 0.5649936169491524, + "variance": 0.017329359925297418, + "sd": 0.13164102675570946, + "sem": 0.0044250647063860315, + "df": 884, + "critical": 1.96, + "moe": 0.008673126824516621, + "p75": 0.5542259999999715, + "p99": 1.0561989999998787, + "p995": 1.1007520000000568, + "p999": 1.3723669999999402, + "sampleCount": 885, + "median": 0.5261639999998806 + }, + { + "id": "-1068277940_0_1", + "name": "fsm", + "rank": 1, + "rme": 0.6133533190779474, + "samples": [], + "totalTime": 500.00123599994686, + "min": 0.002124999999978172, + "max": 0.4388850000000275, + "hz": 364125.0998827918, + "period": 0.0027463088930751818, + "mean": 0.0027463088930751818, + "variance": 0.000013447134783211087, + "sd": 0.0036670335126926623, + "sem": 0.000008594171810106872, + "df": 182062, + "critical": 1.96, + "moe": 0.000016844576747809467, + "p75": 0.00261000000000422, + "p99": 0.005317999999988388, + "p995": 0.0074489999997240375, + "p999": 0.023729999999886786, + "sampleCount": 182063, + "median": 0.0025460000001658045 + } + ] + }, + { + "fullName": "packages/parser/src/__bench__/tokenizer.bench.ts > tokenize medium (1777 chars)", + "benchmarks": [ + { + "id": "-1068277940_1_0", + "name": "regex", + "rank": 2, + "rme": 3.524667752133275, + "samples": [], + "totalTime": 518.7961389999991, + "min": 19.4718170000001, + "max": 28.74305000000004, + "hz": 48.188485072746545, + "period": 20.751845559999964, + "mean": 20.751845559999964, + "variance": 3.139571992830767, + "sd": 1.7718837413416173, + "sem": 0.35437674826832344, + "df": 24, + "critical": 2.064, + "moe": 0.7314336084258196, + "p75": 20.659483999999793, + "p99": 28.74305000000004, + "p995": 28.74305000000004, + "p999": 28.74305000000004, + "sampleCount": 25, + "median": 20.37862300000006 + }, + { + "id": "-1068277940_1_1", + "name": "fsm", + "rank": 1, + "rme": 0.6624735904903833, + "samples": [], + "totalTime": 500.0035550000025, + "min": 0.03033499999992273, + "max": 0.5823090000003504, + "hz": 26333.812766591098, + "period": 0.03797399217741342, + "mean": 0.03797399217741342, + "variance": 0.0002169123830559907, + "sd": 0.014727945649546331, + "sem": 0.00012835085175012654, + "df": 13166, + "critical": 1.96, + "moe": 0.000251567669430248, + "p75": 0.036670999999842024, + "p99": 0.06638299999985975, + "p995": 0.0816330000002381, + "p999": 0.2839810000000398, + "sampleCount": 13167, + "median": 0.035983999999643856 + } + ] + }, + { + "fullName": "packages/parser/src/__bench__/tokenizer.bench.ts > tokenize keywordHeavy (2511 chars)", + "benchmarks": [ + { + "id": "-1068277940_2_0", + "name": "regex", + "rank": 2, + "rme": 1.7877956419477135, + "samples": [], + "totalTime": 515.2327339999997, + "min": 29.07310099999995, + "max": 33.27790499999992, + "hz": 32.99479803625988, + "period": 30.307807882352925, + "mean": 30.307807882352925, + "variance": 1.1105087871830166, + "sd": 1.053806807333781, + "sem": 0.2555856926842411, + "df": 16, + "critical": 2.12, + "moe": 0.5418416684905911, + "p75": 30.90304600000036, + "p99": 33.27790499999992, + "p995": 33.27790499999992, + "p999": 33.27790499999992, + "sampleCount": 17, + "median": 30.187858000000233 + }, + { + "id": "-1068277940_2_1", + "name": "fsm", + "rank": 1, + "rme": 0.6175740445255259, + "samples": [], + "totalTime": 500.0284290000027, + "min": 0.04476100000010774, + "max": 0.5986629999997604, + "hz": 20106.856764337987, + "period": 0.04973427779988091, + "mean": 0.04973427779988091, + "variance": 0.0002468973565791904, + "sd": 0.015712967783941722, + "sem": 0.00015670713822667616, + "df": 10053, + "critical": 1.96, + "moe": 0.00030714599092428527, + "p75": 0.047118000000409666, + "p99": 0.0876379999999699, + "p995": 0.10085900000012771, + "p999": 0.3146649999998772, + "sampleCount": 10054, + "median": 0.04607899999973597 + } + ] + }, + { + "fullName": "packages/parser/src/__bench__/tokenizer.bench.ts > tokenize stringHeavy (1410 chars)", + "benchmarks": [ + { + "id": "-1068277940_3_0", + "name": "regex", + "rank": 2, + "rme": 0.7766105799152846, + "samples": [], + "totalTime": 503.6576939999986, + "min": 6.501181999999972, + "max": 7.724500999999691, + "hz": 142.95423430978144, + "period": 6.995245749999981, + "mean": 6.995245749999981, + "variance": 0.05341669000115708, + "sd": 0.2311205096938761, + "sem": 0.027237813279305165, + "df": 71, + "critical": 1.9945, + "moe": 0.05432581858557415, + "p75": 7.049482999999782, + "p99": 7.724500999999691, + "p995": 7.724500999999691, + "p999": 7.724500999999691, + "sampleCount": 72, + "median": 6.967653500000324 + }, + { + "id": "-1068277940_3_1", + "name": "fsm", + "rank": 1, + "rme": 0.5493915324505317, + "samples": [], + "totalTime": 500.00108399988767, + "min": 0.01133399999980611, + "max": 0.5432410000003074, + "hz": 69159.8500614606, + "period": 0.014459256333137296, + "mean": 0.014459256333137296, + "variance": 0.00005680266124359761, + "sd": 0.007536754025679597, + "sem": 0.0000405295560967212, + "df": 34579, + "critical": 1.96, + "moe": 0.00007943792994957356, + "p75": 0.01378500000009808, + "p99": 0.03121000000010099, + "p995": 0.03526799999963259, + "p999": 0.052106000000094355, + "sampleCount": 34580, + "median": 0.013637999999446038 + } + ] + }, + { + "fullName": "packages/parser/src/__bench__/tokenizer.bench.ts > tokenize large (14262 chars)", + "benchmarks": [ + { + "id": "-1068277940_4_0", + "name": "regex", + "rank": 2, + "rme": 0.9425965082975333, + "samples": [], + "totalTime": 9050.40537, + "min": 884.817156000001, + "max": 926.1038919999992, + "hz": 1.104922883691783, + "period": 905.0405370000001, + "mean": 905.0405370000001, + "variance": 142.2337026237795, + "sd": 11.92617720075379, + "sem": 3.7713883733153164, + "df": 9, + "critical": 2.262, + "moe": 8.530880500439245, + "p75": 914.2840939999987, + "p99": 926.1038919999992, + "p995": 926.1038919999992, + "p999": 926.1038919999992, + "sampleCount": 10, + "median": 903.8145745000011 + }, + { + "id": "-1068277940_4_1", + "name": "fsm", + "rank": 1, + "rme": 0.7733163929814905, + "samples": [], + "totalTime": 500.2985060000319, + "min": 0.24359799999729148, + "max": 0.892772999999579, + "hz": 3280.041775699197, + "period": 0.30487416575260934, + "mean": 0.30487416575260934, + "variance": 0.002374390365389728, + "sd": 0.04872771660348685, + "sem": 0.0012028785212910658, + "df": 1640, + "critical": 1.96, + "moe": 0.002357641901730489, + "p75": 0.31062000000019907, + "p99": 0.5434040000000095, + "p995": 0.5940250000021479, + "p999": 0.6961689999989176, + "sampleCount": 1641, + "median": 0.2921910000004573 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/parser/src/__bench__/fixtures/index.ts b/packages/parser/src/__bench__/fixtures/index.ts new file mode 100644 index 0000000..ed0178f --- /dev/null +++ b/packages/parser/src/__bench__/fixtures/index.ts @@ -0,0 +1,33 @@ +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +function load(name: string): string { + return readFileSync(resolve(__dirname, name), 'utf-8') +} + +export const tinySource = load('./tiny.vjs') +export const mediumSource = load('./medium.vjs') +export const keywordHeavySource = load('./keyword-heavy.vjs') +export const stringHeavySource = load('./string-heavy.vjs') + +export const largeSource = (() => { + const block = mediumSource + const blocks: string[] = [] + for (let i = 0; i < 8; i++) { + blocks.push(block.replace(/\bdiện tích\b/g, `diện tích${i}`)) + } + return blocks.join('\n\n') +})() + +export const fixtures = { + tiny: tinySource, + medium: mediumSource, + keywordHeavy: keywordHeavySource, + stringHeavy: stringHeavySource, + large: largeSource, +} as const + +export type FixtureName = keyof typeof fixtures diff --git a/packages/parser/src/__bench__/fixtures/keyword-heavy.vjs b/packages/parser/src/__bench__/fixtures/keyword-heavy.vjs new file mode 100644 index 0000000..acc9a28 --- /dev/null +++ b/packages/parser/src/__bench__/fixtures/keyword-heavy.vjs @@ -0,0 +1,98 @@ +// Worst-case: identifier đa-từ + keyword đa-từ chen lẫn nhau +// Đây là kịch bản truncateBeforeEmbeddedKeyword bị gọi nhiều nhất + +khai báo con mèo đẹp = 1 +khai báo con chó nhanh = 2 +khai báo con cá vàng to = 3 +khai báo bầu trời xanh = 4 +khai báo đại dương sâu thẳm = 5 +khai báo dòng sông quê hương = 6 +khai báo ngọn núi cao chót vót = 7 +khai báo cánh đồng lúa mơn mởn = 8 +khai báo cây tre trăm đốt = 9 +khai báo bông hoa nhỏ xinh = 10 +khai báo ánh mặt trời rực rỡ = 11 +khai báo đêm trăng huyền ảo = 12 +khai báo gió thu se lạnh = 13 +khai báo mưa rào tháng sáu = 14 +khai báo tuyết trắng phương bắc = 15 +khai báo lửa hồng bếp xưa = 16 +khai báo đóa hồng nhung = 17 +khai báo dòng suối trong vắt = 18 +khai báo tiếng chim ban mai = 19 +khai báo sương sớm buổi bình minh = 20 + +biến phương thức của tôi = hàm() { trả về 0 } +biến phương thức xử lý dữ liệu = hàm() { trả về 1 } +biến phương thức kiểm tra điều kiện = hàm() { trả về 2 } +biến phương thức tính toán giá trị = hàm() { trả về 3 } + +hằng số con đường dài và xa = "đến đó" +hằng số kết quả cuối cùng tốt = "ok" +hằng số trạng thái hiện tại của hệ thống = "active" + +lớp Con Mèo Đẹp Trai { + riêng tư danh hiệu của nó + công khai số tuổi hiện tại = 0 + công khai màu lông tự nhiên = "đen" + bảo vệ vùng lãnh thổ riêng = [] + + khởi tạo(tên gọi cho nó) { + đây.danh hiệu của nó = tên gọi cho nó + } + + lấy danh hiệu chính thức() { + trả về đây.danh hiệu của nó + } + + gán danh hiệu chính thức(giá trị mới) { + đây.danh hiệu của nó = giá trị mới + } + + tĩnh tạo con mèo mới(tên) { + trả về new Con Mèo Đẹp Trai(tên) + } +} + +lớp Con Chó Nhanh Nhẹn kế thừa Con Mèo Đẹp Trai { + bất đồng bộ chạy thật nhanh đi(quãng đường) { + chờ ngủ một chút(100) + trả về quãng đường * 2 + } +} + +lặp (khai báo từng phần tử của danh sách dài) { + nếu (từng phần tử là kiểu Con Mèo Đẹp Trai) { + console.log("là mèo") + tiếp tục + } + nếu (từng phần tử trong vùng lãnh thổ riêng) { + phá vòng lặp + } +} + +khi mà (điều kiện vẫn còn đúng) { + khai báo kết quả tạm thời = phương thức xử lý dữ liệu() + nếu (kết quả tạm thời === rỗng) { + phá vòng lặp + } +} + +duyệt (trạng thái hiện tại của hệ thống) { + trường hợp "đang chạy": + console.log("ok") + phá vòng lặp + trường hợp "tạm dừng": + console.log("paused") + phá vòng lặp + mặc định: + console.log("unknown") +} + +thử { + khai báo dữ liệu nhận được = chờ tải dữ liệu từ máy chủ() +} bắt lỗi (lỗi xảy ra ngẫu nhiên) { + báo lỗi mới Lỗi Hệ Thống(lỗi xảy ra ngẫu nhiên) +} cuối cùng { + dọn dẹp tài nguyên đã sử dụng() +} diff --git a/packages/parser/src/__bench__/fixtures/medium.vjs b/packages/parser/src/__bench__/fixtures/medium.vjs new file mode 100644 index 0000000..dbe8ee8 --- /dev/null +++ b/packages/parser/src/__bench__/fixtures/medium.vjs @@ -0,0 +1,101 @@ +// Fixture trung bình mô phỏng script đời thực +// Kết hợp keyword EN + VI, biểu thức, lớp, vòng lặp + +khai báo PI = 3.14159 +hằng số E = 2.71828 +biến đếm = 0 + +hàm tính diện tích(bán kính) { + trả về PI * bán kính * bán kính +} + +hàm cộng(a, b) { + trả về a + b +} + +const cube = (x) => x ** 3 +const square = function (x) { return x * x } + +lớp Hình Tròn { + khởi tạo(bán kính) { + đây.bán kính = bán kính + } + + lấy diện tích() { + trả về tính diện tích(đây.bán kính) + } + + tĩnh tạo từ đường kính(d) { + trả về new Hình Tròn(d / 2) + } +} + +lớp Tam Giác kế thừa Hình Tròn { + riêng tư cạnh + công khai tên = "tam giác" + + khởi tạo(cạnh) { + khởi tạo cha(cạnh) + đây.cạnh = cạnh + } +} + +lặp (khai báo i = 0; i < 100; i++) { + đếm = đếm + i + nếu (i % 2 === 0) { + tiếp tục + } + nếu (i > 50) { + phá vòng lặp + } +} + +lặp (khai báo phần tử của [1, 2, 3, 4, 5]) { + console.log(phần tử) +} + +khi mà (đếm > 0) { + đếm-- +} + +duyệt (đếm) { + trường hợp 0: + console.log("zero") + phá vòng lặp + trường hợp 1: + trường hợp 2: + console.log("nhỏ") + phá vòng lặp + mặc định: + console.log("khác") +} + +thử { + khai báo x = JSON.parse('{"a":1}') +} bắt lỗi (lỗi) { + console.error(lỗi) +} cuối cùng { + console.log("xong") +} + +bất đồng bộ hàm fetchData(url) { + khai báo res = chờ fetch(url) + trả về chờ res.json() +} + +khai báo arr = [1, 2, 3, ...[4, 5, 6]] +khai báo obj = { a: 1, b: 2, c: { d: 3 } } +khai báo { a, b, ...rest } = obj +khai báo [first, second, ...others] = arr + +khai báo result = đếm > 50 ? "lớn" : đếm > 10 ? "trung bình" : "nhỏ" + +khai báo greeting = `Xin chào ${tên}, bạn đang ${đếm} tuổi` + +khai báo regex = /^[a-z]+$/gi + +cho phép default Hình Tròn +cho phép { Tam Giác, tính diện tích } + +sử dụng Foo từ "./foo" +sử dụng { Bar, Baz như là QualifiedBaz } từ "./bar" diff --git a/packages/parser/src/__bench__/fixtures/string-heavy.vjs b/packages/parser/src/__bench__/fixtures/string-heavy.vjs new file mode 100644 index 0000000..02da065 --- /dev/null +++ b/packages/parser/src/__bench__/fixtures/string-heavy.vjs @@ -0,0 +1,39 @@ +// Fixture nặng chuỗi, template literal, regex literal +khai báo a = "chuỗi đơn giản" +khai báo b = 'chuỗi bằng nháy đơn' +khai báo c = "chứa \"escape\" và \\backslash" +khai báo d = "unicode á ắ" +khai báo e = "\n\t\r dòng mới" + +khai báo t1 = `template thường` +khai báo t2 = `xen kẽ ${a} và ${b}` +khai báo t3 = `lồng ${`bên trong ${c} thêm`} ngoài` +khai báo t4 = `nhiều dòng +qua nhiều +khúc khác nhau ${1 + 2}` + +khai báo t5 = `phức tạp ${{ a: 1, b: [2, 3] }} thêm` + +khai báo r1 = /^abc$/ +khai báo r2 = /[a-zA-Z0-9_-]+/gi +khai báo r3 = /\d+\.\d+/ +khai báo r4 = /(?:foo|bar)\b/i + +khai báo divisor = a / b / c +khai báo sum = (a + b) / c + +khai báo bigText = "lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + + "ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " + + "nisi ut aliquip ex ea commodo consequat. duis aute irure dolor in " + + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " + + "pariatur. excepteur sint occaecat cupidatat non proident, sunt in culpa " + + "qui officia deserunt mollit anim id est laborum." + +khai báo arr = [ + "alpha", "bravo", "charlie", "delta", "echo", + "foxtrot", "golf", "hotel", "india", "juliet", + "kilo", "lima", "mike", "november", "oscar", + "papa", "quebec", "romeo", "sierra", "tango", + "uniform", "victor", "whiskey", "xray", "yankee", "zulu" +] diff --git a/packages/parser/src/__bench__/fixtures/tiny.vjs b/packages/parser/src/__bench__/fixtures/tiny.vjs new file mode 100644 index 0000000..ca55654 --- /dev/null +++ b/packages/parser/src/__bench__/fixtures/tiny.vjs @@ -0,0 +1,10 @@ +khai báo tuổi = 25 +khai báo tên = "Tú Nguyễn" + +hàm chào(người) { + trả về "Xin chào " + người +} + +nếu (tuổi > 18) { + chào(tên) +} diff --git a/packages/parser/src/__bench__/parser.bench.ts b/packages/parser/src/__bench__/parser.bench.ts new file mode 100644 index 0000000..eb2766e --- /dev/null +++ b/packages/parser/src/__bench__/parser.bench.ts @@ -0,0 +1,18 @@ +import { Parser } from '@parser/parser' +import { bench, describe } from 'vitest' + +import { fixtures } from './fixtures' + +// Note: parser bench uses only fixtures that successfully build a full AST. +// The other fixtures still exercise the tokenizer (see tokenizer.bench.ts). +const PARSE_OK = ['tiny'] as const + +describe('parser end-to-end (regex baseline)', () => { + for (const name of PARSE_OK) { + const source = fixtures[name] + bench(`parse ${name} (${source.length} chars)`, () => { + const parser = new Parser() + parser.parse(source) + }) + } +}) diff --git a/packages/parser/src/__bench__/tokenizer.bench.ts b/packages/parser/src/__bench__/tokenizer.bench.ts new file mode 100644 index 0000000..f55bbf4 --- /dev/null +++ b/packages/parser/src/__bench__/tokenizer.bench.ts @@ -0,0 +1,47 @@ +import type { Token } from '@vietscript/shared' +import { Parser } from '@parser/parser' +import { Tokenizer } from '@parser/tokenizer' +import { TokenizerFSM } from '@parser/tokenizer-fsm' +import { bench, describe } from 'vitest' + +import { fixtures } from './fixtures' + +function tokenizeRegex(source: string): number { + const parser = new Parser() + parser.syntax = source + parser.tokenizer = new Tokenizer(parser) + let count = 0 + let tok: Token | null = parser.tokenizer.getNextToken() + while (tok !== null) { + count++ + tok = parser.tokenizer.getNextToken() + } + return count +} + +function tokenizeFSM(source: string): number { + const parser = new Parser() + parser.syntax = source + parser.tokenizer = new TokenizerFSM(parser) + let count = 0 + let tok: Token | null = parser.tokenizer.getNextToken() + while (tok !== null) { + count++ + tok = parser.tokenizer.getNextToken() + } + return count +} + +const NAMES = ['tiny', 'medium', 'keywordHeavy', 'stringHeavy', 'large'] as const + +for (const name of NAMES) { + const source = fixtures[name] + describe(`tokenize ${name} (${source.length} chars)`, () => { + bench('regex', () => { + tokenizeRegex(source) + }) + bench('fsm', () => { + tokenizeFSM(source) + }) + }) +} diff --git a/packages/parser/src/__test__/__snapshots__/bench-fixture-snapshot.test.ts.snap b/packages/parser/src/__test__/__snapshots__/bench-fixture-snapshot.test.ts.snap new file mode 100644 index 0000000..f812b0c --- /dev/null +++ b/packages/parser/src/__test__/__snapshots__/bench-fixture-snapshot.test.ts.snap @@ -0,0 +1,4254 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`bench fixtures token-stream snapshots > stable snapshot: keywordHeavy 1`] = ` +[ + "Var|khai báo|130-138", + "Identifier|con mèo đẹp|139-150", + "=|=|151-152", + "Number|1|153-154", + "Var|khai báo|155-163", + "Identifier|con chó nhanh|164-177", + "=|=|178-179", + "Number|2|180-181", + "Var|khai báo|182-190", + "Identifier|con cá vàng to|191-205", + "=|=|206-207", + "Number|3|208-209", + "Var|khai báo|210-218", + "Identifier|bầu trời xanh|219-232", + "=|=|233-234", + "Number|4|235-236", + "Var|khai báo|237-245", + "Identifier|đại dương sâu thẳm|246-264", + "=|=|265-266", + "Number|5|267-268", + "Var|khai báo|269-277", + "Identifier|dòng sông quê hương|278-297", + "=|=|298-299", + "Number|6|300-301", + "Var|khai báo|302-310", + "Identifier|ngọn núi cao chót vót|311-332", + "=|=|333-334", + "Number|7|335-336", + "Var|khai báo|337-345", + "Identifier|cánh đồng lúa mơn mởn|346-367", + "=|=|368-369", + "Number|8|370-371", + "Var|khai báo|372-380", + "Identifier|cây tre trăm đốt|381-397", + "=|=|398-399", + "Number|9|400-401", + "Var|khai báo|402-410", + "Identifier|bông hoa nhỏ xinh|411-428", + "=|=|429-430", + "Number|10|431-433", + "Var|khai báo|434-442", + "Identifier|ánh mặt trời rực rỡ|443-462", + "=|=|463-464", + "Number|11|465-467", + "Var|khai báo|468-476", + "Identifier|đêm trăng huyền ảo|477-495", + "=|=|496-497", + "Number|12|498-500", + "Var|khai báo|501-509", + "Identifier|gió thu se lạnh|510-525", + "=|=|526-527", + "Number|13|528-530", + "Var|khai báo|531-539", + "Identifier|mưa rào tháng sáu|540-557", + "=|=|558-559", + "Number|14|560-562", + "Var|khai báo|563-571", + "Identifier|tuyết trắng phương bắc|572-594", + "=|=|595-596", + "Number|15|597-599", + "Var|khai báo|600-608", + "Identifier|lửa hồng bếp xưa|609-625", + "=|=|626-627", + "Number|16|628-630", + "Var|khai báo|631-639", + "Identifier|đóa hồng nhung|640-654", + "=|=|655-656", + "Number|17|657-659", + "Var|khai báo|660-668", + "Identifier|dòng suối|669-678", + "In|trong|679-684", + "Identifier|vắt|685-688", + "=|=|689-690", + "Number|18|691-693", + "Var|khai báo|694-702", + "Identifier|tiếng chim ban mai|703-721", + "=|=|722-723", + "Number|19|724-726", + "Var|khai báo|727-735", + "Identifier|sương sớm buổi bình minh|736-760", + "=|=|761-762", + "Number|20|763-765", + "Let|biến|767-771", + "Identifier|phương thức|772-783", + "Of|của|784-787", + "Identifier|tôi|788-791", + "=|=|792-793", + "Function|hàm|794-797", + "(|(|797-798", + ")|)|798-799", + "{|{|800-801", + "Return|trả về|802-808", + "Number|0|809-810", + "}|}|811-812", + "Let|biến|813-817", + "Identifier|phương thức xử lý dữ liệu|818-843", + "=|=|844-845", + "Function|hàm|846-849", + "(|(|849-850", + ")|)|850-851", + "{|{|852-853", + "Return|trả về|854-860", + "Number|1|861-862", + "}|}|863-864", + "Let|biến|865-869", + "Identifier|phương thức kiểm tra điều kiện|870-900", + "=|=|901-902", + "Function|hàm|903-906", + "(|(|906-907", + ")|)|907-908", + "{|{|909-910", + "Return|trả về|911-917", + "Number|2|918-919", + "}|}|920-921", + "Let|biến|922-926", + "Identifier|phương thức tính toán giá trị|927-956", + "=|=|957-958", + "Function|hàm|959-962", + "(|(|962-963", + ")|)|963-964", + "{|{|965-966", + "Return|trả về|967-973", + "Number|3|974-975", + "}|}|976-977", + "Const|hằng số|979-986", + "Identifier|con đường dài và xa|987-1006", + "=|=|1007-1008", + "String|"đến đó"|1009-1017", + "Const|hằng số|1018-1025", + "Identifier|kết quả|1026-1033", + "Finally|cuối cùng|1034-1043", + "Identifier|tốt|1044-1047", + "=|=|1048-1049", + "String|"ok"|1050-1054", + "Const|hằng số|1055-1062", + "Identifier|trạng thái hiện tại|1063-1082", + "Of|của|1083-1086", + "Identifier|hệ thống|1087-1095", + "=|=|1096-1097", + "String|"active"|1098-1106", + "Class|lớp|1108-1111", + "Identifier|Con Mèo Đẹp Trai|1112-1128", + "{|{|1129-1130", + "Private|riêng tư|1133-1141", + "Identifier|danh hiệu|1142-1151", + "Of|của|1152-1155", + "Identifier|nó|1156-1158", + "Public|công khai|1161-1170", + "Identifier|số tuổi hiện tại|1171-1187", + "=|=|1188-1189", + "Number|0|1190-1191", + "Public|công khai|1194-1203", + "Identifier|màu lông tự nhiên|1204-1221", + "=|=|1222-1223", + "String|"đen"|1224-1229", + "Protected|bảo vệ|1232-1238", + "Identifier|vùng lãnh thổ riêng|1239-1258", + "=|=|1259-1260", + "[|[|1261-1262", + "]|]|1262-1263", + "Constructor|khởi tạo|1267-1275", + "(|(|1275-1276", + "Identifier|tên gọi cho nó|1276-1290", + ")|)|1290-1291", + "{|{|1292-1293", + "This|đây|1298-1301", + ".|.|1301-1302", + "Identifier|danh hiệu|1302-1311", + "Of|của|1312-1315", + "Identifier|nó|1316-1318", + "=|=|1319-1320", + "Identifier|tên gọi cho nó|1321-1335", + "}|}|1338-1339", + "Get|lấy|1343-1346", + "Identifier|danh hiệu chính thức|1347-1367", + "(|(|1367-1368", + ")|)|1368-1369", + "{|{|1370-1371", + "Return|trả về|1376-1382", + "This|đây|1383-1386", + ".|.|1386-1387", + "Identifier|danh hiệu|1387-1396", + "Of|của|1397-1400", + "Identifier|nó|1401-1403", + "}|}|1406-1407", + "Set|gán|1411-1414", + "Identifier|danh hiệu chính thức|1415-1435", + "(|(|1435-1436", + "Identifier|giá trị mới|1436-1447", + ")|)|1447-1448", + "{|{|1449-1450", + "This|đây|1455-1458", + ".|.|1458-1459", + "Identifier|danh hiệu|1459-1468", + "Of|của|1469-1472", + "Identifier|nó|1473-1475", + "=|=|1476-1477", + "Identifier|giá trị mới|1478-1489", + "}|}|1492-1493", + "Static|tĩnh|1497-1501", + "Identifier|tạo con mèo mới|1502-1517", + "(|(|1517-1518", + "Identifier|tên|1518-1521", + ")|)|1521-1522", + "{|{|1523-1524", + "Return|trả về|1529-1535", + "New|new|1536-1539", + "Identifier|Con Mèo Đẹp Trai|1540-1556", + "(|(|1556-1557", + "Identifier|tên|1557-1560", + ")|)|1560-1561", + "}|}|1564-1565", + "}|}|1566-1567", + "Class|lớp|1569-1572", + "Identifier|Con Chó Nhanh Nhẹn|1573-1591", + "Extends|kế thừa|1592-1599", + "Identifier|Con Mèo Đẹp Trai|1600-1616", + "{|{|1617-1618", + "Async|bất đồng bộ|1621-1632", + "Identifier|chạy thật nhanh đi|1633-1651", + "(|(|1651-1652", + "Identifier|quãng đường|1652-1663", + ")|)|1663-1664", + "{|{|1665-1666", + "Await|chờ|1671-1674", + "Identifier|ngủ một chút|1675-1687", + "(|(|1687-1688", + "Number|100|1688-1691", + ")|)|1691-1692", + "Return|trả về|1697-1703", + "Identifier|quãng đường|1704-1715", + "*|*|1716-1717", + "Number|2|1718-1719", + "}|}|1722-1723", + "}|}|1724-1725", + "For|lặp|1727-1730", + "(|(|1731-1732", + "Var|khai báo|1732-1740", + "From|từ|1741-1743", + "Identifier|ng phần tử|1743-1753", + "Of|của|1754-1757", + "Identifier|danh sách dài|1758-1771", + ")|)|1771-1772", + "{|{|1773-1774", + "If|nếu|1777-1780", + "(|(|1781-1782", + "From|từ|1782-1784", + "Identifier|ng phần tử|1784-1794", + "Instanceof|là kiểu|1795-1802", + "Identifier|Con Mèo Đẹp Trai|1803-1819", + ")|)|1819-1820", + "{|{|1821-1822", + "Identifier|console|1827-1834", + ".|.|1834-1835", + "Identifier|log|1835-1838", + "(|(|1838-1839", + "String|"là mèo"|1839-1847", + ")|)|1847-1848", + "Continue|tiếp tục|1853-1861", + "}|}|1864-1865", + "If|nếu|1868-1871", + "(|(|1872-1873", + "From|từ|1873-1875", + "Identifier|ng phần tử|1875-1885", + "In|trong|1886-1891", + "Identifier|vùng lãnh thổ riêng|1892-1911", + ")|)|1911-1912", + "{|{|1913-1914", + "Break|phá vòng lặp|1919-1931", + "}|}|1934-1935", + "}|}|1936-1937", + "While|khi mà|1939-1945", + "(|(|1946-1947", + "Identifier|điều kiện vẫn còn|1947-1964", + "Boolean|đúng|1965-1969", + ")|)|1969-1970", + "{|{|1971-1972", + "Var|khai báo|1975-1983", + "Identifier|kết quả tạm thời|1984-2000", + "=|=|2001-2002", + "Identifier|phương thức xử lý dữ liệu|2003-2028", + "(|(|2028-2029", + ")|)|2029-2030", + "If|nếu|2033-2036", + "(|(|2037-2038", + "Identifier|kết quả tạm thời|2038-2054", + "===|===|2055-2058", + "Null|rỗng|2059-2063", + ")|)|2063-2064", + "{|{|2065-2066", + "Break|phá vòng lặp|2071-2083", + "}|}|2086-2087", + "}|}|2088-2089", + "Switch|duyệt|2091-2096", + "(|(|2097-2098", + "Identifier|trạng thái hiện tại|2098-2117", + "Of|của|2118-2121", + "Identifier|hệ thống|2122-2130", + ")|)|2130-2131", + "{|{|2132-2133", + "Case|trường hợp|2136-2146", + "String|"đang chạy"|2147-2158", + ":|:|2158-2159", + "Identifier|console|2164-2171", + ".|.|2171-2172", + "Identifier|log|2172-2175", + "(|(|2175-2176", + "String|"ok"|2176-2180", + ")|)|2180-2181", + "Break|phá vòng lặp|2186-2198", + "Case|trường hợp|2201-2211", + "String|"tạm dừng"|2212-2222", + ":|:|2222-2223", + "Identifier|console|2228-2235", + ".|.|2235-2236", + "Identifier|log|2236-2239", + "(|(|2239-2240", + "String|"paused"|2240-2248", + ")|)|2248-2249", + "Break|phá vòng lặp|2254-2266", + "Default|mặc định|2269-2277", + ":|:|2277-2278", + "Identifier|console|2283-2290", + ".|.|2290-2291", + "Identifier|log|2291-2294", + "(|(|2294-2295", + "String|"unknown"|2295-2304", + ")|)|2304-2305", + "}|}|2306-2307", + "Try|thử|2309-2312", + "{|{|2313-2314", + "Var|khai báo|2317-2325", + "Identifier|dữ liệu nhận được|2326-2343", + "=|=|2344-2345", + "Await|chờ|2346-2349", + "Identifier|tải dữ liệu|2350-2361", + "From|từ|2362-2364", + "Identifier|máy chủ|2365-2372", + "(|(|2372-2373", + ")|)|2373-2374", + "}|}|2375-2376", + "Catch|bắt lỗi|2377-2384", + "(|(|2385-2386", + "Identifier|lỗi xảy ra ngẫu nhiên|2386-2407", + ")|)|2407-2408", + "{|{|2409-2410", + "Throw|báo lỗi|2413-2420", + "Identifier|mới Lỗi Hệ Thống|2421-2437", + "(|(|2437-2438", + "Identifier|lỗi xảy ra ngẫu nhiên|2438-2459", + ")|)|2459-2460", + "}|}|2461-2462", + "Finally|cuối cùng|2463-2472", + "{|{|2473-2474", + "Identifier|dọn dẹp tài nguyên đã|2477-2498", + "Import|sử dụng|2499-2506", + "(|(|2506-2507", + ")|)|2507-2508", + "}|}|2509-2510", +] +`; + +exports[`bench fixtures token-stream snapshots > stable snapshot: large 1`] = ` +[ + "Var|khai báo|101-109", + "Identifier|PI|110-112", + "=|=|113-114", + "Number|3.14159|115-122", + "Const|hằng số|123-130", + "Identifier|E|131-132", + "=|=|133-134", + "Number|2.71828|135-142", + "Let|biến|143-147", + "Identifier|đếm|148-151", + "=|=|152-153", + "Number|0|154-155", + "Function|hàm|157-160", + "Identifier|tính diện tích0|161-176", + "(|(|176-177", + "Identifier|bán kính|177-185", + ")|)|185-186", + "{|{|187-188", + "Return|trả về|191-197", + "Identifier|PI|198-200", + "*|*|201-202", + "Identifier|bán kính|203-211", + "*|*|212-213", + "Identifier|bán kính|214-222", + "}|}|223-224", + "Function|hàm|226-229", + "Identifier|cộng|230-234", + "(|(|234-235", + "Identifier|a|235-236", + ",|,|236-237", + "Identifier|b|238-239", + ")|)|239-240", + "{|{|241-242", + "Return|trả về|245-251", + "Identifier|a|252-253", + "+|+|254-255", + "Identifier|b|256-257", + "}|}|258-259", + "Const|const|261-266", + "Identifier|cube|267-271", + "=|=|272-273", + "(|(|274-275", + "Identifier|x|275-276", + ")|)|276-277", + "=>|=>|278-280", + "Identifier|x|281-282", + "**|**|283-285", + "Number|3|286-287", + "Const|const|288-293", + "Identifier|square|294-300", + "=|=|301-302", + "Function|function|303-311", + "(|(|312-313", + "Identifier|x|313-314", + ")|)|314-315", + "{|{|316-317", + "Return|return|318-324", + "Identifier|x|325-326", + "*|*|327-328", + "Identifier|x|329-330", + "}|}|331-332", + "Class|lớp|334-337", + "Identifier|Hình Tròn|338-347", + "{|{|348-349", + "Constructor|khởi tạo|352-360", + "(|(|360-361", + "Identifier|bán kính|361-369", + ")|)|369-370", + "{|{|371-372", + "This|đây|377-380", + ".|.|380-381", + "Identifier|bán kính|381-389", + "=|=|390-391", + "Identifier|bán kính|392-400", + "}|}|403-404", + "Get|lấy|408-411", + "Identifier|diện tích0|412-422", + "(|(|422-423", + ")|)|423-424", + "{|{|425-426", + "Return|trả về|431-437", + "Identifier|tính diện tích0|438-453", + "(|(|453-454", + "This|đây|454-457", + ".|.|457-458", + "Identifier|bán kính|458-466", + ")|)|466-467", + "}|}|470-471", + "Static|tĩnh|475-479", + "Identifier|tạo|480-483", + "From|từ|484-486", + "Identifier|đường kính|487-497", + "(|(|497-498", + "Identifier|d|498-499", + ")|)|499-500", + "{|{|501-502", + "Return|trả về|507-513", + "New|new|514-517", + "Identifier|Hình Tròn|518-527", + "(|(|527-528", + "Identifier|d|528-529", + "/|/|530-531", + "Number|2|532-533", + ")|)|533-534", + "}|}|537-538", + "}|}|539-540", + "Class|lớp|542-545", + "Identifier|Tam Giác|546-554", + "Extends|kế thừa|555-562", + "Identifier|Hình Tròn|563-572", + "{|{|573-574", + "Private|riêng tư|577-585", + "Identifier|cạnh|586-590", + "Public|công khai|593-602", + "Identifier|tên|603-606", + "=|=|607-608", + "String|"tam giác"|609-619", + "Constructor|khởi tạo|623-631", + "(|(|631-632", + "Identifier|cạnh|632-636", + ")|)|636-637", + "{|{|638-639", + "Super|khởi tạo cha|644-656", + "(|(|656-657", + "Identifier|cạnh|657-661", + ")|)|661-662", + "This|đây|667-670", + ".|.|670-671", + "Identifier|cạnh|671-675", + "=|=|676-677", + "Identifier|cạnh|678-682", + "}|}|685-686", + "}|}|687-688", + "For|lặp|690-693", + "(|(|694-695", + "Var|khai báo|695-703", + "Identifier|i|704-705", + "=|=|706-707", + "Number|0|708-709", + ";|;|709-710", + "Identifier|i|711-712", + "<|<|713-714", + "Number|100|715-718", + ";|;|718-719", + "Identifier|i|720-721", + "++|++|721-723", + ")|)|723-724", + "{|{|725-726", + "Identifier|đếm|729-732", + "=|=|733-734", + "Identifier|đếm|735-738", + "+|+|739-740", + "Identifier|i|741-742", + "If|nếu|745-748", + "(|(|749-750", + "Identifier|i|750-751", + "%|%|752-753", + "Number|2|754-755", + "===|===|756-759", + "Number|0|760-761", + ")|)|761-762", + "{|{|763-764", + "Continue|tiếp tục|769-777", + "}|}|780-781", + "If|nếu|784-787", + "(|(|788-789", + "Identifier|i|789-790", + ">|>|791-792", + "Number|50|793-795", + ")|)|795-796", + "{|{|797-798", + "Break|phá vòng lặp|803-815", + "}|}|818-819", + "}|}|820-821", + "For|lặp|823-826", + "(|(|827-828", + "Var|khai báo|828-836", + "Identifier|phần tử|837-844", + "Of|của|845-848", + "[|[|849-850", + "Number|1|850-851", + ",|,|851-852", + "Number|2|853-854", + ",|,|854-855", + "Number|3|856-857", + ",|,|857-858", + "Number|4|859-860", + ",|,|860-861", + "Number|5|862-863", + "]|]|863-864", + ")|)|864-865", + "{|{|866-867", + "Identifier|console|870-877", + ".|.|877-878", + "Identifier|log|878-881", + "(|(|881-882", + "Identifier|phần tử|882-889", + ")|)|889-890", + "}|}|891-892", + "While|khi mà|894-900", + "(|(|901-902", + "Identifier|đếm|902-905", + ">|>|906-907", + "Number|0|908-909", + ")|)|909-910", + "{|{|911-912", + "Identifier|đếm|915-918", + "--|--|918-920", + "}|}|921-922", + "Switch|duyệt|924-929", + "(|(|930-931", + "Identifier|đếm|931-934", + ")|)|934-935", + "{|{|936-937", + "Case|trường hợp|940-950", + "Number|0|951-952", + ":|:|952-953", + "Identifier|console|958-965", + ".|.|965-966", + "Identifier|log|966-969", + "(|(|969-970", + "String|"zero"|970-976", + ")|)|976-977", + "Break|phá vòng lặp|982-994", + "Case|trường hợp|997-1007", + "Number|1|1008-1009", + ":|:|1009-1010", + "Case|trường hợp|1013-1023", + "Number|2|1024-1025", + ":|:|1025-1026", + "Identifier|console|1031-1038", + ".|.|1038-1039", + "Identifier|log|1039-1042", + "(|(|1042-1043", + "String|"nhỏ"|1043-1048", + ")|)|1048-1049", + "Break|phá vòng lặp|1054-1066", + "Default|mặc định|1069-1077", + ":|:|1077-1078", + "Identifier|console|1083-1090", + ".|.|1090-1091", + "Identifier|log|1091-1094", + "(|(|1094-1095", + "String|"khác"|1095-1101", + ")|)|1101-1102", + "}|}|1103-1104", + "Try|thử|1106-1109", + "{|{|1110-1111", + "Var|khai báo|1114-1122", + "Identifier|x|1123-1124", + "=|=|1125-1126", + "Identifier|JSON|1127-1131", + ".|.|1131-1132", + "Identifier|parse|1132-1137", + "(|(|1137-1138", + "String|'{"a":1}'|1138-1147", + ")|)|1147-1148", + "}|}|1149-1150", + "Catch|bắt lỗi|1151-1158", + "(|(|1159-1160", + "Identifier|lỗi|1160-1163", + ")|)|1163-1164", + "{|{|1165-1166", + "Identifier|console|1169-1176", + ".|.|1176-1177", + "Identifier|error|1177-1182", + "(|(|1182-1183", + "Identifier|lỗi|1183-1186", + ")|)|1186-1187", + "}|}|1188-1189", + "Finally|cuối cùng|1190-1199", + "{|{|1200-1201", + "Identifier|console|1204-1211", + ".|.|1211-1212", + "Identifier|log|1212-1215", + "(|(|1215-1216", + "String|"xong"|1216-1222", + ")|)|1222-1223", + "}|}|1224-1225", + "Async|bất đồng bộ|1227-1238", + "Function|hàm|1239-1242", + "Identifier|fetchData|1243-1252", + "(|(|1252-1253", + "Identifier|url|1253-1256", + ")|)|1256-1257", + "{|{|1258-1259", + "Var|khai báo|1262-1270", + "Identifier|res|1271-1274", + "=|=|1275-1276", + "Await|chờ|1277-1280", + "Identifier|fetch|1281-1286", + "(|(|1286-1287", + "Identifier|url|1287-1290", + ")|)|1290-1291", + "Return|trả về|1294-1300", + "Await|chờ|1301-1304", + "Identifier|res|1305-1308", + ".|.|1308-1309", + "Identifier|json|1309-1313", + "(|(|1313-1314", + ")|)|1314-1315", + "}|}|1316-1317", + "Var|khai báo|1319-1327", + "Identifier|arr|1328-1331", + "=|=|1332-1333", + "[|[|1334-1335", + "Number|1|1335-1336", + ",|,|1336-1337", + "Number|2|1338-1339", + ",|,|1339-1340", + "Number|3|1341-1342", + ",|,|1342-1343", + "...|...|1344-1347", + "[|[|1347-1348", + "Number|4|1348-1349", + ",|,|1349-1350", + "Number|5|1351-1352", + ",|,|1352-1353", + "Number|6|1354-1355", + "]|]|1355-1356", + "]|]|1356-1357", + "Var|khai báo|1358-1366", + "Identifier|obj|1367-1370", + "=|=|1371-1372", + "{|{|1373-1374", + "Identifier|a|1375-1376", + ":|:|1376-1377", + "Number|1|1378-1379", + ",|,|1379-1380", + "Identifier|b|1381-1382", + ":|:|1382-1383", + "Number|2|1384-1385", + ",|,|1385-1386", + "Identifier|c|1387-1388", + ":|:|1388-1389", + "{|{|1390-1391", + "Identifier|d|1392-1393", + ":|:|1393-1394", + "Number|3|1395-1396", + "}|}|1397-1398", + "}|}|1399-1400", + "Var|khai báo|1401-1409", + "{|{|1410-1411", + "Identifier|a|1412-1413", + ",|,|1413-1414", + "Identifier|b|1415-1416", + ",|,|1416-1417", + "...|...|1418-1421", + "Identifier|rest|1421-1425", + "}|}|1426-1427", + "=|=|1428-1429", + "Identifier|obj|1430-1433", + "Var|khai báo|1434-1442", + "[|[|1443-1444", + "Identifier|first|1444-1449", + ",|,|1449-1450", + "Identifier|second|1451-1457", + ",|,|1457-1458", + "...|...|1459-1462", + "Identifier|others|1462-1468", + "]|]|1468-1469", + "=|=|1470-1471", + "Identifier|arr|1472-1475", + "Var|khai báo|1477-1485", + "Identifier|result|1486-1492", + "=|=|1493-1494", + "Identifier|đếm|1495-1498", + ">|>|1499-1500", + "Number|50|1501-1503", + "?|?|1504-1505", + "String|"lớn"|1506-1511", + ":|:|1512-1513", + "Identifier|đếm|1514-1517", + ">|>|1518-1519", + "Number|10|1520-1522", + "?|?|1523-1524", + "String|"trung bình"|1525-1537", + ":|:|1538-1539", + "String|"nhỏ"|1540-1545", + "Var|khai báo|1547-1555", + "Identifier|greeting|1556-1564", + "=|=|1565-1566", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|1567-1606", + "Var|khai báo|1608-1616", + "Identifier|regex|1617-1622", + "=|=|1623-1624", + "RegExpLiteral|/^[a-z]+$/gi|1625-1637", + "Export|cho phép|1639-1647", + "Default|default|1648-1655", + "Identifier|Hình Tròn|1656-1665", + "Export|cho phép|1666-1674", + "{|{|1675-1676", + "Identifier|Tam Giác|1677-1685", + ",|,|1685-1686", + "Identifier|tính diện tích0|1687-1702", + "}|}|1703-1704", + "Import|sử dụng|1706-1713", + "Identifier|Foo|1714-1717", + "From|từ|1718-1720", + "String|"./foo"|1721-1728", + "Import|sử dụng|1729-1736", + "{|{|1737-1738", + "Identifier|Bar|1739-1742", + ",|,|1742-1743", + "Identifier|Baz|1744-1747", + "As|như là|1748-1754", + "Identifier|QualifiedBaz|1755-1767", + "}|}|1768-1769", + "From|từ|1770-1772", + "String|"./bar"|1773-1780", + "Var|khai báo|1884-1892", + "Identifier|PI|1893-1895", + "=|=|1896-1897", + "Number|3.14159|1898-1905", + "Const|hằng số|1906-1913", + "Identifier|E|1914-1915", + "=|=|1916-1917", + "Number|2.71828|1918-1925", + "Let|biến|1926-1930", + "Identifier|đếm|1931-1934", + "=|=|1935-1936", + "Number|0|1937-1938", + "Function|hàm|1940-1943", + "Identifier|tính diện tích1|1944-1959", + "(|(|1959-1960", + "Identifier|bán kính|1960-1968", + ")|)|1968-1969", + "{|{|1970-1971", + "Return|trả về|1974-1980", + "Identifier|PI|1981-1983", + "*|*|1984-1985", + "Identifier|bán kính|1986-1994", + "*|*|1995-1996", + "Identifier|bán kính|1997-2005", + "}|}|2006-2007", + "Function|hàm|2009-2012", + "Identifier|cộng|2013-2017", + "(|(|2017-2018", + "Identifier|a|2018-2019", + ",|,|2019-2020", + "Identifier|b|2021-2022", + ")|)|2022-2023", + "{|{|2024-2025", + "Return|trả về|2028-2034", + "Identifier|a|2035-2036", + "+|+|2037-2038", + "Identifier|b|2039-2040", + "}|}|2041-2042", + "Const|const|2044-2049", + "Identifier|cube|2050-2054", + "=|=|2055-2056", + "(|(|2057-2058", + "Identifier|x|2058-2059", + ")|)|2059-2060", + "=>|=>|2061-2063", + "Identifier|x|2064-2065", + "**|**|2066-2068", + "Number|3|2069-2070", + "Const|const|2071-2076", + "Identifier|square|2077-2083", + "=|=|2084-2085", + "Function|function|2086-2094", + "(|(|2095-2096", + "Identifier|x|2096-2097", + ")|)|2097-2098", + "{|{|2099-2100", + "Return|return|2101-2107", + "Identifier|x|2108-2109", + "*|*|2110-2111", + "Identifier|x|2112-2113", + "}|}|2114-2115", + "Class|lớp|2117-2120", + "Identifier|Hình Tròn|2121-2130", + "{|{|2131-2132", + "Constructor|khởi tạo|2135-2143", + "(|(|2143-2144", + "Identifier|bán kính|2144-2152", + ")|)|2152-2153", + "{|{|2154-2155", + "This|đây|2160-2163", + ".|.|2163-2164", + "Identifier|bán kính|2164-2172", + "=|=|2173-2174", + "Identifier|bán kính|2175-2183", + "}|}|2186-2187", + "Get|lấy|2191-2194", + "Identifier|diện tích1|2195-2205", + "(|(|2205-2206", + ")|)|2206-2207", + "{|{|2208-2209", + "Return|trả về|2214-2220", + "Identifier|tính diện tích1|2221-2236", + "(|(|2236-2237", + "This|đây|2237-2240", + ".|.|2240-2241", + "Identifier|bán kính|2241-2249", + ")|)|2249-2250", + "}|}|2253-2254", + "Static|tĩnh|2258-2262", + "Identifier|tạo|2263-2266", + "From|từ|2267-2269", + "Identifier|đường kính|2270-2280", + "(|(|2280-2281", + "Identifier|d|2281-2282", + ")|)|2282-2283", + "{|{|2284-2285", + "Return|trả về|2290-2296", + "New|new|2297-2300", + "Identifier|Hình Tròn|2301-2310", + "(|(|2310-2311", + "Identifier|d|2311-2312", + "/|/|2313-2314", + "Number|2|2315-2316", + ")|)|2316-2317", + "}|}|2320-2321", + "}|}|2322-2323", + "Class|lớp|2325-2328", + "Identifier|Tam Giác|2329-2337", + "Extends|kế thừa|2338-2345", + "Identifier|Hình Tròn|2346-2355", + "{|{|2356-2357", + "Private|riêng tư|2360-2368", + "Identifier|cạnh|2369-2373", + "Public|công khai|2376-2385", + "Identifier|tên|2386-2389", + "=|=|2390-2391", + "String|"tam giác"|2392-2402", + "Constructor|khởi tạo|2406-2414", + "(|(|2414-2415", + "Identifier|cạnh|2415-2419", + ")|)|2419-2420", + "{|{|2421-2422", + "Super|khởi tạo cha|2427-2439", + "(|(|2439-2440", + "Identifier|cạnh|2440-2444", + ")|)|2444-2445", + "This|đây|2450-2453", + ".|.|2453-2454", + "Identifier|cạnh|2454-2458", + "=|=|2459-2460", + "Identifier|cạnh|2461-2465", + "}|}|2468-2469", + "}|}|2470-2471", + "For|lặp|2473-2476", + "(|(|2477-2478", + "Var|khai báo|2478-2486", + "Identifier|i|2487-2488", + "=|=|2489-2490", + "Number|0|2491-2492", + ";|;|2492-2493", + "Identifier|i|2494-2495", + "<|<|2496-2497", + "Number|100|2498-2501", + ";|;|2501-2502", + "Identifier|i|2503-2504", + "++|++|2504-2506", + ")|)|2506-2507", + "{|{|2508-2509", + "Identifier|đếm|2512-2515", + "=|=|2516-2517", + "Identifier|đếm|2518-2521", + "+|+|2522-2523", + "Identifier|i|2524-2525", + "If|nếu|2528-2531", + "(|(|2532-2533", + "Identifier|i|2533-2534", + "%|%|2535-2536", + "Number|2|2537-2538", + "===|===|2539-2542", + "Number|0|2543-2544", + ")|)|2544-2545", + "{|{|2546-2547", + "Continue|tiếp tục|2552-2560", + "}|}|2563-2564", + "If|nếu|2567-2570", + "(|(|2571-2572", + "Identifier|i|2572-2573", + ">|>|2574-2575", + "Number|50|2576-2578", + ")|)|2578-2579", + "{|{|2580-2581", + "Break|phá vòng lặp|2586-2598", + "}|}|2601-2602", + "}|}|2603-2604", + "For|lặp|2606-2609", + "(|(|2610-2611", + "Var|khai báo|2611-2619", + "Identifier|phần tử|2620-2627", + "Of|của|2628-2631", + "[|[|2632-2633", + "Number|1|2633-2634", + ",|,|2634-2635", + "Number|2|2636-2637", + ",|,|2637-2638", + "Number|3|2639-2640", + ",|,|2640-2641", + "Number|4|2642-2643", + ",|,|2643-2644", + "Number|5|2645-2646", + "]|]|2646-2647", + ")|)|2647-2648", + "{|{|2649-2650", + "Identifier|console|2653-2660", + ".|.|2660-2661", + "Identifier|log|2661-2664", + "(|(|2664-2665", + "Identifier|phần tử|2665-2672", + ")|)|2672-2673", + "}|}|2674-2675", + "While|khi mà|2677-2683", + "(|(|2684-2685", + "Identifier|đếm|2685-2688", + ">|>|2689-2690", + "Number|0|2691-2692", + ")|)|2692-2693", + "{|{|2694-2695", + "Identifier|đếm|2698-2701", + "--|--|2701-2703", + "}|}|2704-2705", + "Switch|duyệt|2707-2712", + "(|(|2713-2714", + "Identifier|đếm|2714-2717", + ")|)|2717-2718", + "{|{|2719-2720", + "Case|trường hợp|2723-2733", + "Number|0|2734-2735", + ":|:|2735-2736", + "Identifier|console|2741-2748", + ".|.|2748-2749", + "Identifier|log|2749-2752", + "(|(|2752-2753", + "String|"zero"|2753-2759", + ")|)|2759-2760", + "Break|phá vòng lặp|2765-2777", + "Case|trường hợp|2780-2790", + "Number|1|2791-2792", + ":|:|2792-2793", + "Case|trường hợp|2796-2806", + "Number|2|2807-2808", + ":|:|2808-2809", + "Identifier|console|2814-2821", + ".|.|2821-2822", + "Identifier|log|2822-2825", + "(|(|2825-2826", + "String|"nhỏ"|2826-2831", + ")|)|2831-2832", + "Break|phá vòng lặp|2837-2849", + "Default|mặc định|2852-2860", + ":|:|2860-2861", + "Identifier|console|2866-2873", + ".|.|2873-2874", + "Identifier|log|2874-2877", + "(|(|2877-2878", + "String|"khác"|2878-2884", + ")|)|2884-2885", + "}|}|2886-2887", + "Try|thử|2889-2892", + "{|{|2893-2894", + "Var|khai báo|2897-2905", + "Identifier|x|2906-2907", + "=|=|2908-2909", + "Identifier|JSON|2910-2914", + ".|.|2914-2915", + "Identifier|parse|2915-2920", + "(|(|2920-2921", + "String|'{"a":1}'|2921-2930", + ")|)|2930-2931", + "}|}|2932-2933", + "Catch|bắt lỗi|2934-2941", + "(|(|2942-2943", + "Identifier|lỗi|2943-2946", + ")|)|2946-2947", + "{|{|2948-2949", + "Identifier|console|2952-2959", + ".|.|2959-2960", + "Identifier|error|2960-2965", + "(|(|2965-2966", + "Identifier|lỗi|2966-2969", + ")|)|2969-2970", + "}|}|2971-2972", + "Finally|cuối cùng|2973-2982", + "{|{|2983-2984", + "Identifier|console|2987-2994", + ".|.|2994-2995", + "Identifier|log|2995-2998", + "(|(|2998-2999", + "String|"xong"|2999-3005", + ")|)|3005-3006", + "}|}|3007-3008", + "Async|bất đồng bộ|3010-3021", + "Function|hàm|3022-3025", + "Identifier|fetchData|3026-3035", + "(|(|3035-3036", + "Identifier|url|3036-3039", + ")|)|3039-3040", + "{|{|3041-3042", + "Var|khai báo|3045-3053", + "Identifier|res|3054-3057", + "=|=|3058-3059", + "Await|chờ|3060-3063", + "Identifier|fetch|3064-3069", + "(|(|3069-3070", + "Identifier|url|3070-3073", + ")|)|3073-3074", + "Return|trả về|3077-3083", + "Await|chờ|3084-3087", + "Identifier|res|3088-3091", + ".|.|3091-3092", + "Identifier|json|3092-3096", + "(|(|3096-3097", + ")|)|3097-3098", + "}|}|3099-3100", + "Var|khai báo|3102-3110", + "Identifier|arr|3111-3114", + "=|=|3115-3116", + "[|[|3117-3118", + "Number|1|3118-3119", + ",|,|3119-3120", + "Number|2|3121-3122", + ",|,|3122-3123", + "Number|3|3124-3125", + ",|,|3125-3126", + "...|...|3127-3130", + "[|[|3130-3131", + "Number|4|3131-3132", + ",|,|3132-3133", + "Number|5|3134-3135", + ",|,|3135-3136", + "Number|6|3137-3138", + "]|]|3138-3139", + "]|]|3139-3140", + "Var|khai báo|3141-3149", + "Identifier|obj|3150-3153", + "=|=|3154-3155", + "{|{|3156-3157", + "Identifier|a|3158-3159", + ":|:|3159-3160", + "Number|1|3161-3162", + ",|,|3162-3163", + "Identifier|b|3164-3165", + ":|:|3165-3166", + "Number|2|3167-3168", + ",|,|3168-3169", + "Identifier|c|3170-3171", + ":|:|3171-3172", + "{|{|3173-3174", + "Identifier|d|3175-3176", + ":|:|3176-3177", + "Number|3|3178-3179", + "}|}|3180-3181", + "}|}|3182-3183", + "Var|khai báo|3184-3192", + "{|{|3193-3194", + "Identifier|a|3195-3196", + ",|,|3196-3197", + "Identifier|b|3198-3199", + ",|,|3199-3200", + "...|...|3201-3204", + "Identifier|rest|3204-3208", + "}|}|3209-3210", + "=|=|3211-3212", + "Identifier|obj|3213-3216", + "Var|khai báo|3217-3225", + "[|[|3226-3227", + "Identifier|first|3227-3232", + ",|,|3232-3233", + "Identifier|second|3234-3240", + ",|,|3240-3241", + "...|...|3242-3245", + "Identifier|others|3245-3251", + "]|]|3251-3252", + "=|=|3253-3254", + "Identifier|arr|3255-3258", + "Var|khai báo|3260-3268", + "Identifier|result|3269-3275", + "=|=|3276-3277", + "Identifier|đếm|3278-3281", + ">|>|3282-3283", + "Number|50|3284-3286", + "?|?|3287-3288", + "String|"lớn"|3289-3294", + ":|:|3295-3296", + "Identifier|đếm|3297-3300", + ">|>|3301-3302", + "Number|10|3303-3305", + "?|?|3306-3307", + "String|"trung bình"|3308-3320", + ":|:|3321-3322", + "String|"nhỏ"|3323-3328", + "Var|khai báo|3330-3338", + "Identifier|greeting|3339-3347", + "=|=|3348-3349", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|3350-3389", + "Var|khai báo|3391-3399", + "Identifier|regex|3400-3405", + "=|=|3406-3407", + "RegExpLiteral|/^[a-z]+$/gi|3408-3420", + "Export|cho phép|3422-3430", + "Default|default|3431-3438", + "Identifier|Hình Tròn|3439-3448", + "Export|cho phép|3449-3457", + "{|{|3458-3459", + "Identifier|Tam Giác|3460-3468", + ",|,|3468-3469", + "Identifier|tính diện tích1|3470-3485", + "}|}|3486-3487", + "Import|sử dụng|3489-3496", + "Identifier|Foo|3497-3500", + "From|từ|3501-3503", + "String|"./foo"|3504-3511", + "Import|sử dụng|3512-3519", + "{|{|3520-3521", + "Identifier|Bar|3522-3525", + ",|,|3525-3526", + "Identifier|Baz|3527-3530", + "As|như là|3531-3537", + "Identifier|QualifiedBaz|3538-3550", + "}|}|3551-3552", + "From|từ|3553-3555", + "String|"./bar"|3556-3563", + "Var|khai báo|3667-3675", + "Identifier|PI|3676-3678", + "=|=|3679-3680", + "Number|3.14159|3681-3688", + "Const|hằng số|3689-3696", + "Identifier|E|3697-3698", + "=|=|3699-3700", + "Number|2.71828|3701-3708", + "Let|biến|3709-3713", + "Identifier|đếm|3714-3717", + "=|=|3718-3719", + "Number|0|3720-3721", + "Function|hàm|3723-3726", + "Identifier|tính diện tích2|3727-3742", + "(|(|3742-3743", + "Identifier|bán kính|3743-3751", + ")|)|3751-3752", + "{|{|3753-3754", + "Return|trả về|3757-3763", + "Identifier|PI|3764-3766", + "*|*|3767-3768", + "Identifier|bán kính|3769-3777", + "*|*|3778-3779", + "Identifier|bán kính|3780-3788", + "}|}|3789-3790", + "Function|hàm|3792-3795", + "Identifier|cộng|3796-3800", + "(|(|3800-3801", + "Identifier|a|3801-3802", + ",|,|3802-3803", + "Identifier|b|3804-3805", + ")|)|3805-3806", + "{|{|3807-3808", + "Return|trả về|3811-3817", + "Identifier|a|3818-3819", + "+|+|3820-3821", + "Identifier|b|3822-3823", + "}|}|3824-3825", + "Const|const|3827-3832", + "Identifier|cube|3833-3837", + "=|=|3838-3839", + "(|(|3840-3841", + "Identifier|x|3841-3842", + ")|)|3842-3843", + "=>|=>|3844-3846", + "Identifier|x|3847-3848", + "**|**|3849-3851", + "Number|3|3852-3853", + "Const|const|3854-3859", + "Identifier|square|3860-3866", + "=|=|3867-3868", + "Function|function|3869-3877", + "(|(|3878-3879", + "Identifier|x|3879-3880", + ")|)|3880-3881", + "{|{|3882-3883", + "Return|return|3884-3890", + "Identifier|x|3891-3892", + "*|*|3893-3894", + "Identifier|x|3895-3896", + "}|}|3897-3898", + "Class|lớp|3900-3903", + "Identifier|Hình Tròn|3904-3913", + "{|{|3914-3915", + "Constructor|khởi tạo|3918-3926", + "(|(|3926-3927", + "Identifier|bán kính|3927-3935", + ")|)|3935-3936", + "{|{|3937-3938", + "This|đây|3943-3946", + ".|.|3946-3947", + "Identifier|bán kính|3947-3955", + "=|=|3956-3957", + "Identifier|bán kính|3958-3966", + "}|}|3969-3970", + "Get|lấy|3974-3977", + "Identifier|diện tích2|3978-3988", + "(|(|3988-3989", + ")|)|3989-3990", + "{|{|3991-3992", + "Return|trả về|3997-4003", + "Identifier|tính diện tích2|4004-4019", + "(|(|4019-4020", + "This|đây|4020-4023", + ".|.|4023-4024", + "Identifier|bán kính|4024-4032", + ")|)|4032-4033", + "}|}|4036-4037", + "Static|tĩnh|4041-4045", + "Identifier|tạo|4046-4049", + "From|từ|4050-4052", + "Identifier|đường kính|4053-4063", + "(|(|4063-4064", + "Identifier|d|4064-4065", + ")|)|4065-4066", + "{|{|4067-4068", + "Return|trả về|4073-4079", + "New|new|4080-4083", + "Identifier|Hình Tròn|4084-4093", + "(|(|4093-4094", + "Identifier|d|4094-4095", + "/|/|4096-4097", + "Number|2|4098-4099", + ")|)|4099-4100", + "}|}|4103-4104", + "}|}|4105-4106", + "Class|lớp|4108-4111", + "Identifier|Tam Giác|4112-4120", + "Extends|kế thừa|4121-4128", + "Identifier|Hình Tròn|4129-4138", + "{|{|4139-4140", + "Private|riêng tư|4143-4151", + "Identifier|cạnh|4152-4156", + "Public|công khai|4159-4168", + "Identifier|tên|4169-4172", + "=|=|4173-4174", + "String|"tam giác"|4175-4185", + "Constructor|khởi tạo|4189-4197", + "(|(|4197-4198", + "Identifier|cạnh|4198-4202", + ")|)|4202-4203", + "{|{|4204-4205", + "Super|khởi tạo cha|4210-4222", + "(|(|4222-4223", + "Identifier|cạnh|4223-4227", + ")|)|4227-4228", + "This|đây|4233-4236", + ".|.|4236-4237", + "Identifier|cạnh|4237-4241", + "=|=|4242-4243", + "Identifier|cạnh|4244-4248", + "}|}|4251-4252", + "}|}|4253-4254", + "For|lặp|4256-4259", + "(|(|4260-4261", + "Var|khai báo|4261-4269", + "Identifier|i|4270-4271", + "=|=|4272-4273", + "Number|0|4274-4275", + ";|;|4275-4276", + "Identifier|i|4277-4278", + "<|<|4279-4280", + "Number|100|4281-4284", + ";|;|4284-4285", + "Identifier|i|4286-4287", + "++|++|4287-4289", + ")|)|4289-4290", + "{|{|4291-4292", + "Identifier|đếm|4295-4298", + "=|=|4299-4300", + "Identifier|đếm|4301-4304", + "+|+|4305-4306", + "Identifier|i|4307-4308", + "If|nếu|4311-4314", + "(|(|4315-4316", + "Identifier|i|4316-4317", + "%|%|4318-4319", + "Number|2|4320-4321", + "===|===|4322-4325", + "Number|0|4326-4327", + ")|)|4327-4328", + "{|{|4329-4330", + "Continue|tiếp tục|4335-4343", + "}|}|4346-4347", + "If|nếu|4350-4353", + "(|(|4354-4355", + "Identifier|i|4355-4356", + ">|>|4357-4358", + "Number|50|4359-4361", + ")|)|4361-4362", + "{|{|4363-4364", + "Break|phá vòng lặp|4369-4381", + "}|}|4384-4385", + "}|}|4386-4387", + "For|lặp|4389-4392", + "(|(|4393-4394", + "Var|khai báo|4394-4402", + "Identifier|phần tử|4403-4410", + "Of|của|4411-4414", + "[|[|4415-4416", + "Number|1|4416-4417", + ",|,|4417-4418", + "Number|2|4419-4420", + ",|,|4420-4421", + "Number|3|4422-4423", + ",|,|4423-4424", + "Number|4|4425-4426", + ",|,|4426-4427", + "Number|5|4428-4429", + "]|]|4429-4430", + ")|)|4430-4431", + "{|{|4432-4433", + "Identifier|console|4436-4443", + ".|.|4443-4444", + "Identifier|log|4444-4447", + "(|(|4447-4448", + "Identifier|phần tử|4448-4455", + ")|)|4455-4456", + "}|}|4457-4458", + "While|khi mà|4460-4466", + "(|(|4467-4468", + "Identifier|đếm|4468-4471", + ">|>|4472-4473", + "Number|0|4474-4475", + ")|)|4475-4476", + "{|{|4477-4478", + "Identifier|đếm|4481-4484", + "--|--|4484-4486", + "}|}|4487-4488", + "Switch|duyệt|4490-4495", + "(|(|4496-4497", + "Identifier|đếm|4497-4500", + ")|)|4500-4501", + "{|{|4502-4503", + "Case|trường hợp|4506-4516", + "Number|0|4517-4518", + ":|:|4518-4519", + "Identifier|console|4524-4531", + ".|.|4531-4532", + "Identifier|log|4532-4535", + "(|(|4535-4536", + "String|"zero"|4536-4542", + ")|)|4542-4543", + "Break|phá vòng lặp|4548-4560", + "Case|trường hợp|4563-4573", + "Number|1|4574-4575", + ":|:|4575-4576", + "Case|trường hợp|4579-4589", + "Number|2|4590-4591", + ":|:|4591-4592", + "Identifier|console|4597-4604", + ".|.|4604-4605", + "Identifier|log|4605-4608", + "(|(|4608-4609", + "String|"nhỏ"|4609-4614", + ")|)|4614-4615", + "Break|phá vòng lặp|4620-4632", + "Default|mặc định|4635-4643", + ":|:|4643-4644", + "Identifier|console|4649-4656", + ".|.|4656-4657", + "Identifier|log|4657-4660", + "(|(|4660-4661", + "String|"khác"|4661-4667", + ")|)|4667-4668", + "}|}|4669-4670", + "Try|thử|4672-4675", + "{|{|4676-4677", + "Var|khai báo|4680-4688", + "Identifier|x|4689-4690", + "=|=|4691-4692", + "Identifier|JSON|4693-4697", + ".|.|4697-4698", + "Identifier|parse|4698-4703", + "(|(|4703-4704", + "String|'{"a":1}'|4704-4713", + ")|)|4713-4714", + "}|}|4715-4716", + "Catch|bắt lỗi|4717-4724", + "(|(|4725-4726", + "Identifier|lỗi|4726-4729", + ")|)|4729-4730", + "{|{|4731-4732", + "Identifier|console|4735-4742", + ".|.|4742-4743", + "Identifier|error|4743-4748", + "(|(|4748-4749", + "Identifier|lỗi|4749-4752", + ")|)|4752-4753", + "}|}|4754-4755", + "Finally|cuối cùng|4756-4765", + "{|{|4766-4767", + "Identifier|console|4770-4777", + ".|.|4777-4778", + "Identifier|log|4778-4781", + "(|(|4781-4782", + "String|"xong"|4782-4788", + ")|)|4788-4789", + "}|}|4790-4791", + "Async|bất đồng bộ|4793-4804", + "Function|hàm|4805-4808", + "Identifier|fetchData|4809-4818", + "(|(|4818-4819", + "Identifier|url|4819-4822", + ")|)|4822-4823", + "{|{|4824-4825", + "Var|khai báo|4828-4836", + "Identifier|res|4837-4840", + "=|=|4841-4842", + "Await|chờ|4843-4846", + "Identifier|fetch|4847-4852", + "(|(|4852-4853", + "Identifier|url|4853-4856", + ")|)|4856-4857", + "Return|trả về|4860-4866", + "Await|chờ|4867-4870", + "Identifier|res|4871-4874", + ".|.|4874-4875", + "Identifier|json|4875-4879", + "(|(|4879-4880", + ")|)|4880-4881", + "}|}|4882-4883", + "Var|khai báo|4885-4893", + "Identifier|arr|4894-4897", + "=|=|4898-4899", + "[|[|4900-4901", + "Number|1|4901-4902", + ",|,|4902-4903", + "Number|2|4904-4905", + ",|,|4905-4906", + "Number|3|4907-4908", + ",|,|4908-4909", + "...|...|4910-4913", + "[|[|4913-4914", + "Number|4|4914-4915", + ",|,|4915-4916", + "Number|5|4917-4918", + ",|,|4918-4919", + "Number|6|4920-4921", + "]|]|4921-4922", + "]|]|4922-4923", + "Var|khai báo|4924-4932", + "Identifier|obj|4933-4936", + "=|=|4937-4938", + "{|{|4939-4940", + "Identifier|a|4941-4942", + ":|:|4942-4943", + "Number|1|4944-4945", + ",|,|4945-4946", + "Identifier|b|4947-4948", + ":|:|4948-4949", + "Number|2|4950-4951", + ",|,|4951-4952", + "Identifier|c|4953-4954", + ":|:|4954-4955", + "{|{|4956-4957", + "Identifier|d|4958-4959", + ":|:|4959-4960", + "Number|3|4961-4962", + "}|}|4963-4964", + "}|}|4965-4966", + "Var|khai báo|4967-4975", + "{|{|4976-4977", + "Identifier|a|4978-4979", + ",|,|4979-4980", + "Identifier|b|4981-4982", + ",|,|4982-4983", + "...|...|4984-4987", + "Identifier|rest|4987-4991", + "}|}|4992-4993", + "=|=|4994-4995", + "Identifier|obj|4996-4999", + "Var|khai báo|5000-5008", + "[|[|5009-5010", + "Identifier|first|5010-5015", + ",|,|5015-5016", + "Identifier|second|5017-5023", + ",|,|5023-5024", + "...|...|5025-5028", + "Identifier|others|5028-5034", + "]|]|5034-5035", + "=|=|5036-5037", + "Identifier|arr|5038-5041", + "Var|khai báo|5043-5051", + "Identifier|result|5052-5058", + "=|=|5059-5060", + "Identifier|đếm|5061-5064", + ">|>|5065-5066", + "Number|50|5067-5069", + "?|?|5070-5071", + "String|"lớn"|5072-5077", + ":|:|5078-5079", + "Identifier|đếm|5080-5083", + ">|>|5084-5085", + "Number|10|5086-5088", + "?|?|5089-5090", + "String|"trung bình"|5091-5103", + ":|:|5104-5105", + "String|"nhỏ"|5106-5111", + "Var|khai báo|5113-5121", + "Identifier|greeting|5122-5130", + "=|=|5131-5132", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|5133-5172", + "Var|khai báo|5174-5182", + "Identifier|regex|5183-5188", + "=|=|5189-5190", + "RegExpLiteral|/^[a-z]+$/gi|5191-5203", + "Export|cho phép|5205-5213", + "Default|default|5214-5221", + "Identifier|Hình Tròn|5222-5231", + "Export|cho phép|5232-5240", + "{|{|5241-5242", + "Identifier|Tam Giác|5243-5251", + ",|,|5251-5252", + "Identifier|tính diện tích2|5253-5268", + "}|}|5269-5270", + "Import|sử dụng|5272-5279", + "Identifier|Foo|5280-5283", + "From|từ|5284-5286", + "String|"./foo"|5287-5294", + "Import|sử dụng|5295-5302", + "{|{|5303-5304", + "Identifier|Bar|5305-5308", + ",|,|5308-5309", + "Identifier|Baz|5310-5313", + "As|như là|5314-5320", + "Identifier|QualifiedBaz|5321-5333", + "}|}|5334-5335", + "From|từ|5336-5338", + "String|"./bar"|5339-5346", + "Var|khai báo|5450-5458", + "Identifier|PI|5459-5461", + "=|=|5462-5463", + "Number|3.14159|5464-5471", + "Const|hằng số|5472-5479", + "Identifier|E|5480-5481", + "=|=|5482-5483", + "Number|2.71828|5484-5491", + "Let|biến|5492-5496", + "Identifier|đếm|5497-5500", + "=|=|5501-5502", + "Number|0|5503-5504", + "Function|hàm|5506-5509", + "Identifier|tính diện tích3|5510-5525", + "(|(|5525-5526", + "Identifier|bán kính|5526-5534", + ")|)|5534-5535", + "{|{|5536-5537", + "Return|trả về|5540-5546", + "Identifier|PI|5547-5549", + "*|*|5550-5551", + "Identifier|bán kính|5552-5560", + "*|*|5561-5562", + "Identifier|bán kính|5563-5571", + "}|}|5572-5573", + "Function|hàm|5575-5578", + "Identifier|cộng|5579-5583", + "(|(|5583-5584", + "Identifier|a|5584-5585", + ",|,|5585-5586", + "Identifier|b|5587-5588", + ")|)|5588-5589", + "{|{|5590-5591", + "Return|trả về|5594-5600", + "Identifier|a|5601-5602", + "+|+|5603-5604", + "Identifier|b|5605-5606", + "}|}|5607-5608", + "Const|const|5610-5615", + "Identifier|cube|5616-5620", + "=|=|5621-5622", + "(|(|5623-5624", + "Identifier|x|5624-5625", + ")|)|5625-5626", + "=>|=>|5627-5629", + "Identifier|x|5630-5631", + "**|**|5632-5634", + "Number|3|5635-5636", + "Const|const|5637-5642", + "Identifier|square|5643-5649", + "=|=|5650-5651", + "Function|function|5652-5660", + "(|(|5661-5662", + "Identifier|x|5662-5663", + ")|)|5663-5664", + "{|{|5665-5666", + "Return|return|5667-5673", + "Identifier|x|5674-5675", + "*|*|5676-5677", + "Identifier|x|5678-5679", + "}|}|5680-5681", + "Class|lớp|5683-5686", + "Identifier|Hình Tròn|5687-5696", + "{|{|5697-5698", + "Constructor|khởi tạo|5701-5709", + "(|(|5709-5710", + "Identifier|bán kính|5710-5718", + ")|)|5718-5719", + "{|{|5720-5721", + "This|đây|5726-5729", + ".|.|5729-5730", + "Identifier|bán kính|5730-5738", + "=|=|5739-5740", + "Identifier|bán kính|5741-5749", + "}|}|5752-5753", + "Get|lấy|5757-5760", + "Identifier|diện tích3|5761-5771", + "(|(|5771-5772", + ")|)|5772-5773", + "{|{|5774-5775", + "Return|trả về|5780-5786", + "Identifier|tính diện tích3|5787-5802", + "(|(|5802-5803", + "This|đây|5803-5806", + ".|.|5806-5807", + "Identifier|bán kính|5807-5815", + ")|)|5815-5816", + "}|}|5819-5820", + "Static|tĩnh|5824-5828", + "Identifier|tạo|5829-5832", + "From|từ|5833-5835", + "Identifier|đường kính|5836-5846", + "(|(|5846-5847", + "Identifier|d|5847-5848", + ")|)|5848-5849", + "{|{|5850-5851", + "Return|trả về|5856-5862", + "New|new|5863-5866", + "Identifier|Hình Tròn|5867-5876", + "(|(|5876-5877", + "Identifier|d|5877-5878", + "/|/|5879-5880", + "Number|2|5881-5882", + ")|)|5882-5883", + "}|}|5886-5887", + "}|}|5888-5889", + "Class|lớp|5891-5894", + "Identifier|Tam Giác|5895-5903", + "Extends|kế thừa|5904-5911", + "Identifier|Hình Tròn|5912-5921", + "{|{|5922-5923", + "Private|riêng tư|5926-5934", + "Identifier|cạnh|5935-5939", + "Public|công khai|5942-5951", + "Identifier|tên|5952-5955", + "=|=|5956-5957", + "String|"tam giác"|5958-5968", + "Constructor|khởi tạo|5972-5980", + "(|(|5980-5981", + "Identifier|cạnh|5981-5985", + ")|)|5985-5986", + "{|{|5987-5988", + "Super|khởi tạo cha|5993-6005", + "(|(|6005-6006", + "Identifier|cạnh|6006-6010", + ")|)|6010-6011", + "This|đây|6016-6019", + ".|.|6019-6020", + "Identifier|cạnh|6020-6024", + "=|=|6025-6026", + "Identifier|cạnh|6027-6031", + "}|}|6034-6035", + "}|}|6036-6037", + "For|lặp|6039-6042", + "(|(|6043-6044", + "Var|khai báo|6044-6052", + "Identifier|i|6053-6054", + "=|=|6055-6056", + "Number|0|6057-6058", + ";|;|6058-6059", + "Identifier|i|6060-6061", + "<|<|6062-6063", + "Number|100|6064-6067", + ";|;|6067-6068", + "Identifier|i|6069-6070", + "++|++|6070-6072", + ")|)|6072-6073", + "{|{|6074-6075", + "Identifier|đếm|6078-6081", + "=|=|6082-6083", + "Identifier|đếm|6084-6087", + "+|+|6088-6089", + "Identifier|i|6090-6091", + "If|nếu|6094-6097", + "(|(|6098-6099", + "Identifier|i|6099-6100", + "%|%|6101-6102", + "Number|2|6103-6104", + "===|===|6105-6108", + "Number|0|6109-6110", + ")|)|6110-6111", + "{|{|6112-6113", + "Continue|tiếp tục|6118-6126", + "}|}|6129-6130", + "If|nếu|6133-6136", + "(|(|6137-6138", + "Identifier|i|6138-6139", + ">|>|6140-6141", + "Number|50|6142-6144", + ")|)|6144-6145", + "{|{|6146-6147", + "Break|phá vòng lặp|6152-6164", + "}|}|6167-6168", + "}|}|6169-6170", + "For|lặp|6172-6175", + "(|(|6176-6177", + "Var|khai báo|6177-6185", + "Identifier|phần tử|6186-6193", + "Of|của|6194-6197", + "[|[|6198-6199", + "Number|1|6199-6200", + ",|,|6200-6201", + "Number|2|6202-6203", + ",|,|6203-6204", + "Number|3|6205-6206", + ",|,|6206-6207", + "Number|4|6208-6209", + ",|,|6209-6210", + "Number|5|6211-6212", + "]|]|6212-6213", + ")|)|6213-6214", + "{|{|6215-6216", + "Identifier|console|6219-6226", + ".|.|6226-6227", + "Identifier|log|6227-6230", + "(|(|6230-6231", + "Identifier|phần tử|6231-6238", + ")|)|6238-6239", + "}|}|6240-6241", + "While|khi mà|6243-6249", + "(|(|6250-6251", + "Identifier|đếm|6251-6254", + ">|>|6255-6256", + "Number|0|6257-6258", + ")|)|6258-6259", + "{|{|6260-6261", + "Identifier|đếm|6264-6267", + "--|--|6267-6269", + "}|}|6270-6271", + "Switch|duyệt|6273-6278", + "(|(|6279-6280", + "Identifier|đếm|6280-6283", + ")|)|6283-6284", + "{|{|6285-6286", + "Case|trường hợp|6289-6299", + "Number|0|6300-6301", + ":|:|6301-6302", + "Identifier|console|6307-6314", + ".|.|6314-6315", + "Identifier|log|6315-6318", + "(|(|6318-6319", + "String|"zero"|6319-6325", + ")|)|6325-6326", + "Break|phá vòng lặp|6331-6343", + "Case|trường hợp|6346-6356", + "Number|1|6357-6358", + ":|:|6358-6359", + "Case|trường hợp|6362-6372", + "Number|2|6373-6374", + ":|:|6374-6375", + "Identifier|console|6380-6387", + ".|.|6387-6388", + "Identifier|log|6388-6391", + "(|(|6391-6392", + "String|"nhỏ"|6392-6397", + ")|)|6397-6398", + "Break|phá vòng lặp|6403-6415", + "Default|mặc định|6418-6426", + ":|:|6426-6427", + "Identifier|console|6432-6439", + ".|.|6439-6440", + "Identifier|log|6440-6443", + "(|(|6443-6444", + "String|"khác"|6444-6450", + ")|)|6450-6451", + "}|}|6452-6453", + "Try|thử|6455-6458", + "{|{|6459-6460", + "Var|khai báo|6463-6471", + "Identifier|x|6472-6473", + "=|=|6474-6475", + "Identifier|JSON|6476-6480", + ".|.|6480-6481", + "Identifier|parse|6481-6486", + "(|(|6486-6487", + "String|'{"a":1}'|6487-6496", + ")|)|6496-6497", + "}|}|6498-6499", + "Catch|bắt lỗi|6500-6507", + "(|(|6508-6509", + "Identifier|lỗi|6509-6512", + ")|)|6512-6513", + "{|{|6514-6515", + "Identifier|console|6518-6525", + ".|.|6525-6526", + "Identifier|error|6526-6531", + "(|(|6531-6532", + "Identifier|lỗi|6532-6535", + ")|)|6535-6536", + "}|}|6537-6538", + "Finally|cuối cùng|6539-6548", + "{|{|6549-6550", + "Identifier|console|6553-6560", + ".|.|6560-6561", + "Identifier|log|6561-6564", + "(|(|6564-6565", + "String|"xong"|6565-6571", + ")|)|6571-6572", + "}|}|6573-6574", + "Async|bất đồng bộ|6576-6587", + "Function|hàm|6588-6591", + "Identifier|fetchData|6592-6601", + "(|(|6601-6602", + "Identifier|url|6602-6605", + ")|)|6605-6606", + "{|{|6607-6608", + "Var|khai báo|6611-6619", + "Identifier|res|6620-6623", + "=|=|6624-6625", + "Await|chờ|6626-6629", + "Identifier|fetch|6630-6635", + "(|(|6635-6636", + "Identifier|url|6636-6639", + ")|)|6639-6640", + "Return|trả về|6643-6649", + "Await|chờ|6650-6653", + "Identifier|res|6654-6657", + ".|.|6657-6658", + "Identifier|json|6658-6662", + "(|(|6662-6663", + ")|)|6663-6664", + "}|}|6665-6666", + "Var|khai báo|6668-6676", + "Identifier|arr|6677-6680", + "=|=|6681-6682", + "[|[|6683-6684", + "Number|1|6684-6685", + ",|,|6685-6686", + "Number|2|6687-6688", + ",|,|6688-6689", + "Number|3|6690-6691", + ",|,|6691-6692", + "...|...|6693-6696", + "[|[|6696-6697", + "Number|4|6697-6698", + ",|,|6698-6699", + "Number|5|6700-6701", + ",|,|6701-6702", + "Number|6|6703-6704", + "]|]|6704-6705", + "]|]|6705-6706", + "Var|khai báo|6707-6715", + "Identifier|obj|6716-6719", + "=|=|6720-6721", + "{|{|6722-6723", + "Identifier|a|6724-6725", + ":|:|6725-6726", + "Number|1|6727-6728", + ",|,|6728-6729", + "Identifier|b|6730-6731", + ":|:|6731-6732", + "Number|2|6733-6734", + ",|,|6734-6735", + "Identifier|c|6736-6737", + ":|:|6737-6738", + "{|{|6739-6740", + "Identifier|d|6741-6742", + ":|:|6742-6743", + "Number|3|6744-6745", + "}|}|6746-6747", + "}|}|6748-6749", + "Var|khai báo|6750-6758", + "{|{|6759-6760", + "Identifier|a|6761-6762", + ",|,|6762-6763", + "Identifier|b|6764-6765", + ",|,|6765-6766", + "...|...|6767-6770", + "Identifier|rest|6770-6774", + "}|}|6775-6776", + "=|=|6777-6778", + "Identifier|obj|6779-6782", + "Var|khai báo|6783-6791", + "[|[|6792-6793", + "Identifier|first|6793-6798", + ",|,|6798-6799", + "Identifier|second|6800-6806", + ",|,|6806-6807", + "...|...|6808-6811", + "Identifier|others|6811-6817", + "]|]|6817-6818", + "=|=|6819-6820", + "Identifier|arr|6821-6824", + "Var|khai báo|6826-6834", + "Identifier|result|6835-6841", + "=|=|6842-6843", + "Identifier|đếm|6844-6847", + ">|>|6848-6849", + "Number|50|6850-6852", + "?|?|6853-6854", + "String|"lớn"|6855-6860", + ":|:|6861-6862", + "Identifier|đếm|6863-6866", + ">|>|6867-6868", + "Number|10|6869-6871", + "?|?|6872-6873", + "String|"trung bình"|6874-6886", + ":|:|6887-6888", + "String|"nhỏ"|6889-6894", + "Var|khai báo|6896-6904", + "Identifier|greeting|6905-6913", + "=|=|6914-6915", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|6916-6955", + "Var|khai báo|6957-6965", + "Identifier|regex|6966-6971", + "=|=|6972-6973", + "RegExpLiteral|/^[a-z]+$/gi|6974-6986", + "Export|cho phép|6988-6996", + "Default|default|6997-7004", + "Identifier|Hình Tròn|7005-7014", + "Export|cho phép|7015-7023", + "{|{|7024-7025", + "Identifier|Tam Giác|7026-7034", + ",|,|7034-7035", + "Identifier|tính diện tích3|7036-7051", + "}|}|7052-7053", + "Import|sử dụng|7055-7062", + "Identifier|Foo|7063-7066", + "From|từ|7067-7069", + "String|"./foo"|7070-7077", + "Import|sử dụng|7078-7085", + "{|{|7086-7087", + "Identifier|Bar|7088-7091", + ",|,|7091-7092", + "Identifier|Baz|7093-7096", + "As|như là|7097-7103", + "Identifier|QualifiedBaz|7104-7116", + "}|}|7117-7118", + "From|từ|7119-7121", + "String|"./bar"|7122-7129", + "Var|khai báo|7233-7241", + "Identifier|PI|7242-7244", + "=|=|7245-7246", + "Number|3.14159|7247-7254", + "Const|hằng số|7255-7262", + "Identifier|E|7263-7264", + "=|=|7265-7266", + "Number|2.71828|7267-7274", + "Let|biến|7275-7279", + "Identifier|đếm|7280-7283", + "=|=|7284-7285", + "Number|0|7286-7287", + "Function|hàm|7289-7292", + "Identifier|tính diện tích4|7293-7308", + "(|(|7308-7309", + "Identifier|bán kính|7309-7317", + ")|)|7317-7318", + "{|{|7319-7320", + "Return|trả về|7323-7329", + "Identifier|PI|7330-7332", + "*|*|7333-7334", + "Identifier|bán kính|7335-7343", + "*|*|7344-7345", + "Identifier|bán kính|7346-7354", + "}|}|7355-7356", + "Function|hàm|7358-7361", + "Identifier|cộng|7362-7366", + "(|(|7366-7367", + "Identifier|a|7367-7368", + ",|,|7368-7369", + "Identifier|b|7370-7371", + ")|)|7371-7372", + "{|{|7373-7374", + "Return|trả về|7377-7383", + "Identifier|a|7384-7385", + "+|+|7386-7387", + "Identifier|b|7388-7389", + "}|}|7390-7391", + "Const|const|7393-7398", + "Identifier|cube|7399-7403", + "=|=|7404-7405", + "(|(|7406-7407", + "Identifier|x|7407-7408", + ")|)|7408-7409", + "=>|=>|7410-7412", + "Identifier|x|7413-7414", + "**|**|7415-7417", + "Number|3|7418-7419", + "Const|const|7420-7425", + "Identifier|square|7426-7432", + "=|=|7433-7434", + "Function|function|7435-7443", + "(|(|7444-7445", + "Identifier|x|7445-7446", + ")|)|7446-7447", + "{|{|7448-7449", + "Return|return|7450-7456", + "Identifier|x|7457-7458", + "*|*|7459-7460", + "Identifier|x|7461-7462", + "}|}|7463-7464", + "Class|lớp|7466-7469", + "Identifier|Hình Tròn|7470-7479", + "{|{|7480-7481", + "Constructor|khởi tạo|7484-7492", + "(|(|7492-7493", + "Identifier|bán kính|7493-7501", + ")|)|7501-7502", + "{|{|7503-7504", + "This|đây|7509-7512", + ".|.|7512-7513", + "Identifier|bán kính|7513-7521", + "=|=|7522-7523", + "Identifier|bán kính|7524-7532", + "}|}|7535-7536", + "Get|lấy|7540-7543", + "Identifier|diện tích4|7544-7554", + "(|(|7554-7555", + ")|)|7555-7556", + "{|{|7557-7558", + "Return|trả về|7563-7569", + "Identifier|tính diện tích4|7570-7585", + "(|(|7585-7586", + "This|đây|7586-7589", + ".|.|7589-7590", + "Identifier|bán kính|7590-7598", + ")|)|7598-7599", + "}|}|7602-7603", + "Static|tĩnh|7607-7611", + "Identifier|tạo|7612-7615", + "From|từ|7616-7618", + "Identifier|đường kính|7619-7629", + "(|(|7629-7630", + "Identifier|d|7630-7631", + ")|)|7631-7632", + "{|{|7633-7634", + "Return|trả về|7639-7645", + "New|new|7646-7649", + "Identifier|Hình Tròn|7650-7659", + "(|(|7659-7660", + "Identifier|d|7660-7661", + "/|/|7662-7663", + "Number|2|7664-7665", + ")|)|7665-7666", + "}|}|7669-7670", + "}|}|7671-7672", + "Class|lớp|7674-7677", + "Identifier|Tam Giác|7678-7686", + "Extends|kế thừa|7687-7694", + "Identifier|Hình Tròn|7695-7704", + "{|{|7705-7706", + "Private|riêng tư|7709-7717", + "Identifier|cạnh|7718-7722", + "Public|công khai|7725-7734", + "Identifier|tên|7735-7738", + "=|=|7739-7740", + "String|"tam giác"|7741-7751", + "Constructor|khởi tạo|7755-7763", + "(|(|7763-7764", + "Identifier|cạnh|7764-7768", + ")|)|7768-7769", + "{|{|7770-7771", + "Super|khởi tạo cha|7776-7788", + "(|(|7788-7789", + "Identifier|cạnh|7789-7793", + ")|)|7793-7794", + "This|đây|7799-7802", + ".|.|7802-7803", + "Identifier|cạnh|7803-7807", + "=|=|7808-7809", + "Identifier|cạnh|7810-7814", + "}|}|7817-7818", + "}|}|7819-7820", + "For|lặp|7822-7825", + "(|(|7826-7827", + "Var|khai báo|7827-7835", + "Identifier|i|7836-7837", + "=|=|7838-7839", + "Number|0|7840-7841", + ";|;|7841-7842", + "Identifier|i|7843-7844", + "<|<|7845-7846", + "Number|100|7847-7850", + ";|;|7850-7851", + "Identifier|i|7852-7853", + "++|++|7853-7855", + ")|)|7855-7856", + "{|{|7857-7858", + "Identifier|đếm|7861-7864", + "=|=|7865-7866", + "Identifier|đếm|7867-7870", + "+|+|7871-7872", + "Identifier|i|7873-7874", + "If|nếu|7877-7880", + "(|(|7881-7882", + "Identifier|i|7882-7883", + "%|%|7884-7885", + "Number|2|7886-7887", + "===|===|7888-7891", + "Number|0|7892-7893", + ")|)|7893-7894", + "{|{|7895-7896", + "Continue|tiếp tục|7901-7909", + "}|}|7912-7913", + "If|nếu|7916-7919", + "(|(|7920-7921", + "Identifier|i|7921-7922", + ">|>|7923-7924", + "Number|50|7925-7927", + ")|)|7927-7928", + "{|{|7929-7930", + "Break|phá vòng lặp|7935-7947", + "}|}|7950-7951", + "}|}|7952-7953", + "For|lặp|7955-7958", + "(|(|7959-7960", + "Var|khai báo|7960-7968", + "Identifier|phần tử|7969-7976", + "Of|của|7977-7980", + "[|[|7981-7982", + "Number|1|7982-7983", + ",|,|7983-7984", + "Number|2|7985-7986", + ",|,|7986-7987", + "Number|3|7988-7989", + ",|,|7989-7990", + "Number|4|7991-7992", + ",|,|7992-7993", + "Number|5|7994-7995", + "]|]|7995-7996", + ")|)|7996-7997", + "{|{|7998-7999", + "Identifier|console|8002-8009", + ".|.|8009-8010", + "Identifier|log|8010-8013", + "(|(|8013-8014", + "Identifier|phần tử|8014-8021", + ")|)|8021-8022", + "}|}|8023-8024", + "While|khi mà|8026-8032", + "(|(|8033-8034", + "Identifier|đếm|8034-8037", + ">|>|8038-8039", + "Number|0|8040-8041", + ")|)|8041-8042", + "{|{|8043-8044", + "Identifier|đếm|8047-8050", + "--|--|8050-8052", + "}|}|8053-8054", + "Switch|duyệt|8056-8061", + "(|(|8062-8063", + "Identifier|đếm|8063-8066", + ")|)|8066-8067", + "{|{|8068-8069", + "Case|trường hợp|8072-8082", + "Number|0|8083-8084", + ":|:|8084-8085", + "Identifier|console|8090-8097", + ".|.|8097-8098", + "Identifier|log|8098-8101", + "(|(|8101-8102", + "String|"zero"|8102-8108", + ")|)|8108-8109", + "Break|phá vòng lặp|8114-8126", + "Case|trường hợp|8129-8139", + "Number|1|8140-8141", + ":|:|8141-8142", + "Case|trường hợp|8145-8155", + "Number|2|8156-8157", + ":|:|8157-8158", + "Identifier|console|8163-8170", + ".|.|8170-8171", + "Identifier|log|8171-8174", + "(|(|8174-8175", + "String|"nhỏ"|8175-8180", + ")|)|8180-8181", + "Break|phá vòng lặp|8186-8198", + "Default|mặc định|8201-8209", + ":|:|8209-8210", + "Identifier|console|8215-8222", + ".|.|8222-8223", + "Identifier|log|8223-8226", + "(|(|8226-8227", + "String|"khác"|8227-8233", + ")|)|8233-8234", + "}|}|8235-8236", + "Try|thử|8238-8241", + "{|{|8242-8243", + "Var|khai báo|8246-8254", + "Identifier|x|8255-8256", + "=|=|8257-8258", + "Identifier|JSON|8259-8263", + ".|.|8263-8264", + "Identifier|parse|8264-8269", + "(|(|8269-8270", + "String|'{"a":1}'|8270-8279", + ")|)|8279-8280", + "}|}|8281-8282", + "Catch|bắt lỗi|8283-8290", + "(|(|8291-8292", + "Identifier|lỗi|8292-8295", + ")|)|8295-8296", + "{|{|8297-8298", + "Identifier|console|8301-8308", + ".|.|8308-8309", + "Identifier|error|8309-8314", + "(|(|8314-8315", + "Identifier|lỗi|8315-8318", + ")|)|8318-8319", + "}|}|8320-8321", + "Finally|cuối cùng|8322-8331", + "{|{|8332-8333", + "Identifier|console|8336-8343", + ".|.|8343-8344", + "Identifier|log|8344-8347", + "(|(|8347-8348", + "String|"xong"|8348-8354", + ")|)|8354-8355", + "}|}|8356-8357", + "Async|bất đồng bộ|8359-8370", + "Function|hàm|8371-8374", + "Identifier|fetchData|8375-8384", + "(|(|8384-8385", + "Identifier|url|8385-8388", + ")|)|8388-8389", + "{|{|8390-8391", + "Var|khai báo|8394-8402", + "Identifier|res|8403-8406", + "=|=|8407-8408", + "Await|chờ|8409-8412", + "Identifier|fetch|8413-8418", + "(|(|8418-8419", + "Identifier|url|8419-8422", + ")|)|8422-8423", + "Return|trả về|8426-8432", + "Await|chờ|8433-8436", + "Identifier|res|8437-8440", + ".|.|8440-8441", + "Identifier|json|8441-8445", + "(|(|8445-8446", + ")|)|8446-8447", + "}|}|8448-8449", + "Var|khai báo|8451-8459", + "Identifier|arr|8460-8463", + "=|=|8464-8465", + "[|[|8466-8467", + "Number|1|8467-8468", + ",|,|8468-8469", + "Number|2|8470-8471", + ",|,|8471-8472", + "Number|3|8473-8474", + ",|,|8474-8475", + "...|...|8476-8479", + "[|[|8479-8480", + "Number|4|8480-8481", + ",|,|8481-8482", + "Number|5|8483-8484", + ",|,|8484-8485", + "Number|6|8486-8487", + "]|]|8487-8488", + "]|]|8488-8489", + "Var|khai báo|8490-8498", + "Identifier|obj|8499-8502", + "=|=|8503-8504", + "{|{|8505-8506", + "Identifier|a|8507-8508", + ":|:|8508-8509", + "Number|1|8510-8511", + ",|,|8511-8512", + "Identifier|b|8513-8514", + ":|:|8514-8515", + "Number|2|8516-8517", + ",|,|8517-8518", + "Identifier|c|8519-8520", + ":|:|8520-8521", + "{|{|8522-8523", + "Identifier|d|8524-8525", + ":|:|8525-8526", + "Number|3|8527-8528", + "}|}|8529-8530", + "}|}|8531-8532", + "Var|khai báo|8533-8541", + "{|{|8542-8543", + "Identifier|a|8544-8545", + ",|,|8545-8546", + "Identifier|b|8547-8548", + ",|,|8548-8549", + "...|...|8550-8553", + "Identifier|rest|8553-8557", + "}|}|8558-8559", + "=|=|8560-8561", + "Identifier|obj|8562-8565", + "Var|khai báo|8566-8574", + "[|[|8575-8576", + "Identifier|first|8576-8581", + ",|,|8581-8582", + "Identifier|second|8583-8589", + ",|,|8589-8590", + "...|...|8591-8594", + "Identifier|others|8594-8600", + "]|]|8600-8601", + "=|=|8602-8603", + "Identifier|arr|8604-8607", + "Var|khai báo|8609-8617", + "Identifier|result|8618-8624", + "=|=|8625-8626", + "Identifier|đếm|8627-8630", + ">|>|8631-8632", + "Number|50|8633-8635", + "?|?|8636-8637", + "String|"lớn"|8638-8643", + ":|:|8644-8645", + "Identifier|đếm|8646-8649", + ">|>|8650-8651", + "Number|10|8652-8654", + "?|?|8655-8656", + "String|"trung bình"|8657-8669", + ":|:|8670-8671", + "String|"nhỏ"|8672-8677", + "Var|khai báo|8679-8687", + "Identifier|greeting|8688-8696", + "=|=|8697-8698", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|8699-8738", + "Var|khai báo|8740-8748", + "Identifier|regex|8749-8754", + "=|=|8755-8756", + "RegExpLiteral|/^[a-z]+$/gi|8757-8769", + "Export|cho phép|8771-8779", + "Default|default|8780-8787", + "Identifier|Hình Tròn|8788-8797", + "Export|cho phép|8798-8806", + "{|{|8807-8808", + "Identifier|Tam Giác|8809-8817", + ",|,|8817-8818", + "Identifier|tính diện tích4|8819-8834", + "}|}|8835-8836", + "Import|sử dụng|8838-8845", + "Identifier|Foo|8846-8849", + "From|từ|8850-8852", + "String|"./foo"|8853-8860", + "Import|sử dụng|8861-8868", + "{|{|8869-8870", + "Identifier|Bar|8871-8874", + ",|,|8874-8875", + "Identifier|Baz|8876-8879", + "As|như là|8880-8886", + "Identifier|QualifiedBaz|8887-8899", + "}|}|8900-8901", + "From|từ|8902-8904", + "String|"./bar"|8905-8912", + "Var|khai báo|9016-9024", + "Identifier|PI|9025-9027", + "=|=|9028-9029", + "Number|3.14159|9030-9037", + "Const|hằng số|9038-9045", + "Identifier|E|9046-9047", + "=|=|9048-9049", + "Number|2.71828|9050-9057", + "Let|biến|9058-9062", + "Identifier|đếm|9063-9066", + "=|=|9067-9068", + "Number|0|9069-9070", + "Function|hàm|9072-9075", + "Identifier|tính diện tích5|9076-9091", + "(|(|9091-9092", + "Identifier|bán kính|9092-9100", + ")|)|9100-9101", + "{|{|9102-9103", + "Return|trả về|9106-9112", + "Identifier|PI|9113-9115", + "*|*|9116-9117", + "Identifier|bán kính|9118-9126", + "*|*|9127-9128", + "Identifier|bán kính|9129-9137", + "}|}|9138-9139", + "Function|hàm|9141-9144", + "Identifier|cộng|9145-9149", + "(|(|9149-9150", + "Identifier|a|9150-9151", + ",|,|9151-9152", + "Identifier|b|9153-9154", + ")|)|9154-9155", + "{|{|9156-9157", + "Return|trả về|9160-9166", + "Identifier|a|9167-9168", + "+|+|9169-9170", + "Identifier|b|9171-9172", + "}|}|9173-9174", + "Const|const|9176-9181", + "Identifier|cube|9182-9186", + "=|=|9187-9188", + "(|(|9189-9190", + "Identifier|x|9190-9191", + ")|)|9191-9192", + "=>|=>|9193-9195", + "Identifier|x|9196-9197", + "**|**|9198-9200", + "Number|3|9201-9202", + "Const|const|9203-9208", + "Identifier|square|9209-9215", + "=|=|9216-9217", + "Function|function|9218-9226", + "(|(|9227-9228", + "Identifier|x|9228-9229", + ")|)|9229-9230", + "{|{|9231-9232", + "Return|return|9233-9239", + "Identifier|x|9240-9241", + "*|*|9242-9243", + "Identifier|x|9244-9245", + "}|}|9246-9247", + "Class|lớp|9249-9252", + "Identifier|Hình Tròn|9253-9262", + "{|{|9263-9264", + "Constructor|khởi tạo|9267-9275", + "(|(|9275-9276", + "Identifier|bán kính|9276-9284", + ")|)|9284-9285", + "{|{|9286-9287", + "This|đây|9292-9295", + ".|.|9295-9296", + "Identifier|bán kính|9296-9304", + "=|=|9305-9306", + "Identifier|bán kính|9307-9315", + "}|}|9318-9319", + "Get|lấy|9323-9326", + "Identifier|diện tích5|9327-9337", + "(|(|9337-9338", + ")|)|9338-9339", + "{|{|9340-9341", + "Return|trả về|9346-9352", + "Identifier|tính diện tích5|9353-9368", + "(|(|9368-9369", + "This|đây|9369-9372", + ".|.|9372-9373", + "Identifier|bán kính|9373-9381", + ")|)|9381-9382", + "}|}|9385-9386", + "Static|tĩnh|9390-9394", + "Identifier|tạo|9395-9398", + "From|từ|9399-9401", + "Identifier|đường kính|9402-9412", + "(|(|9412-9413", + "Identifier|d|9413-9414", + ")|)|9414-9415", + "{|{|9416-9417", + "Return|trả về|9422-9428", + "New|new|9429-9432", + "Identifier|Hình Tròn|9433-9442", + "(|(|9442-9443", + "Identifier|d|9443-9444", + "/|/|9445-9446", + "Number|2|9447-9448", + ")|)|9448-9449", + "}|}|9452-9453", + "}|}|9454-9455", + "Class|lớp|9457-9460", + "Identifier|Tam Giác|9461-9469", + "Extends|kế thừa|9470-9477", + "Identifier|Hình Tròn|9478-9487", + "{|{|9488-9489", + "Private|riêng tư|9492-9500", + "Identifier|cạnh|9501-9505", + "Public|công khai|9508-9517", + "Identifier|tên|9518-9521", + "=|=|9522-9523", + "String|"tam giác"|9524-9534", + "Constructor|khởi tạo|9538-9546", + "(|(|9546-9547", + "Identifier|cạnh|9547-9551", + ")|)|9551-9552", + "{|{|9553-9554", + "Super|khởi tạo cha|9559-9571", + "(|(|9571-9572", + "Identifier|cạnh|9572-9576", + ")|)|9576-9577", + "This|đây|9582-9585", + ".|.|9585-9586", + "Identifier|cạnh|9586-9590", + "=|=|9591-9592", + "Identifier|cạnh|9593-9597", + "}|}|9600-9601", + "}|}|9602-9603", + "For|lặp|9605-9608", + "(|(|9609-9610", + "Var|khai báo|9610-9618", + "Identifier|i|9619-9620", + "=|=|9621-9622", + "Number|0|9623-9624", + ";|;|9624-9625", + "Identifier|i|9626-9627", + "<|<|9628-9629", + "Number|100|9630-9633", + ";|;|9633-9634", + "Identifier|i|9635-9636", + "++|++|9636-9638", + ")|)|9638-9639", + "{|{|9640-9641", + "Identifier|đếm|9644-9647", + "=|=|9648-9649", + "Identifier|đếm|9650-9653", + "+|+|9654-9655", + "Identifier|i|9656-9657", + "If|nếu|9660-9663", + "(|(|9664-9665", + "Identifier|i|9665-9666", + "%|%|9667-9668", + "Number|2|9669-9670", + "===|===|9671-9674", + "Number|0|9675-9676", + ")|)|9676-9677", + "{|{|9678-9679", + "Continue|tiếp tục|9684-9692", + "}|}|9695-9696", + "If|nếu|9699-9702", + "(|(|9703-9704", + "Identifier|i|9704-9705", + ">|>|9706-9707", + "Number|50|9708-9710", + ")|)|9710-9711", + "{|{|9712-9713", + "Break|phá vòng lặp|9718-9730", + "}|}|9733-9734", + "}|}|9735-9736", + "For|lặp|9738-9741", + "(|(|9742-9743", + "Var|khai báo|9743-9751", + "Identifier|phần tử|9752-9759", + "Of|của|9760-9763", + "[|[|9764-9765", + "Number|1|9765-9766", + ",|,|9766-9767", + "Number|2|9768-9769", + ",|,|9769-9770", + "Number|3|9771-9772", + ",|,|9772-9773", + "Number|4|9774-9775", + ",|,|9775-9776", + "Number|5|9777-9778", + "]|]|9778-9779", + ")|)|9779-9780", + "{|{|9781-9782", + "Identifier|console|9785-9792", + ".|.|9792-9793", + "Identifier|log|9793-9796", + "(|(|9796-9797", + "Identifier|phần tử|9797-9804", + ")|)|9804-9805", + "}|}|9806-9807", + "While|khi mà|9809-9815", + "(|(|9816-9817", + "Identifier|đếm|9817-9820", + ">|>|9821-9822", + "Number|0|9823-9824", + ")|)|9824-9825", + "{|{|9826-9827", + "Identifier|đếm|9830-9833", + "--|--|9833-9835", + "}|}|9836-9837", + "Switch|duyệt|9839-9844", + "(|(|9845-9846", + "Identifier|đếm|9846-9849", + ")|)|9849-9850", + "{|{|9851-9852", + "Case|trường hợp|9855-9865", + "Number|0|9866-9867", + ":|:|9867-9868", + "Identifier|console|9873-9880", + ".|.|9880-9881", + "Identifier|log|9881-9884", + "(|(|9884-9885", + "String|"zero"|9885-9891", + ")|)|9891-9892", + "Break|phá vòng lặp|9897-9909", + "Case|trường hợp|9912-9922", + "Number|1|9923-9924", + ":|:|9924-9925", + "Case|trường hợp|9928-9938", + "Number|2|9939-9940", + ":|:|9940-9941", + "Identifier|console|9946-9953", + ".|.|9953-9954", + "Identifier|log|9954-9957", + "(|(|9957-9958", + "String|"nhỏ"|9958-9963", + ")|)|9963-9964", + "Break|phá vòng lặp|9969-9981", + "Default|mặc định|9984-9992", + ":|:|9992-9993", + "Identifier|console|9998-10005", + ".|.|10005-10006", + "Identifier|log|10006-10009", + "(|(|10009-10010", + "String|"khác"|10010-10016", + ")|)|10016-10017", + "}|}|10018-10019", + "Try|thử|10021-10024", + "{|{|10025-10026", + "Var|khai báo|10029-10037", + "Identifier|x|10038-10039", + "=|=|10040-10041", + "Identifier|JSON|10042-10046", + ".|.|10046-10047", + "Identifier|parse|10047-10052", + "(|(|10052-10053", + "String|'{"a":1}'|10053-10062", + ")|)|10062-10063", + "}|}|10064-10065", + "Catch|bắt lỗi|10066-10073", + "(|(|10074-10075", + "Identifier|lỗi|10075-10078", + ")|)|10078-10079", + "{|{|10080-10081", + "Identifier|console|10084-10091", + ".|.|10091-10092", + "Identifier|error|10092-10097", + "(|(|10097-10098", + "Identifier|lỗi|10098-10101", + ")|)|10101-10102", + "}|}|10103-10104", + "Finally|cuối cùng|10105-10114", + "{|{|10115-10116", + "Identifier|console|10119-10126", + ".|.|10126-10127", + "Identifier|log|10127-10130", + "(|(|10130-10131", + "String|"xong"|10131-10137", + ")|)|10137-10138", + "}|}|10139-10140", + "Async|bất đồng bộ|10142-10153", + "Function|hàm|10154-10157", + "Identifier|fetchData|10158-10167", + "(|(|10167-10168", + "Identifier|url|10168-10171", + ")|)|10171-10172", + "{|{|10173-10174", + "Var|khai báo|10177-10185", + "Identifier|res|10186-10189", + "=|=|10190-10191", + "Await|chờ|10192-10195", + "Identifier|fetch|10196-10201", + "(|(|10201-10202", + "Identifier|url|10202-10205", + ")|)|10205-10206", + "Return|trả về|10209-10215", + "Await|chờ|10216-10219", + "Identifier|res|10220-10223", + ".|.|10223-10224", + "Identifier|json|10224-10228", + "(|(|10228-10229", + ")|)|10229-10230", + "}|}|10231-10232", + "Var|khai báo|10234-10242", + "Identifier|arr|10243-10246", + "=|=|10247-10248", + "[|[|10249-10250", + "Number|1|10250-10251", + ",|,|10251-10252", + "Number|2|10253-10254", + ",|,|10254-10255", + "Number|3|10256-10257", + ",|,|10257-10258", + "...|...|10259-10262", + "[|[|10262-10263", + "Number|4|10263-10264", + ",|,|10264-10265", + "Number|5|10266-10267", + ",|,|10267-10268", + "Number|6|10269-10270", + "]|]|10270-10271", + "]|]|10271-10272", + "Var|khai báo|10273-10281", + "Identifier|obj|10282-10285", + "=|=|10286-10287", + "{|{|10288-10289", + "Identifier|a|10290-10291", + ":|:|10291-10292", + "Number|1|10293-10294", + ",|,|10294-10295", + "Identifier|b|10296-10297", + ":|:|10297-10298", + "Number|2|10299-10300", + ",|,|10300-10301", + "Identifier|c|10302-10303", + ":|:|10303-10304", + "{|{|10305-10306", + "Identifier|d|10307-10308", + ":|:|10308-10309", + "Number|3|10310-10311", + "}|}|10312-10313", + "}|}|10314-10315", + "Var|khai báo|10316-10324", + "{|{|10325-10326", + "Identifier|a|10327-10328", + ",|,|10328-10329", + "Identifier|b|10330-10331", + ",|,|10331-10332", + "...|...|10333-10336", + "Identifier|rest|10336-10340", + "}|}|10341-10342", + "=|=|10343-10344", + "Identifier|obj|10345-10348", + "Var|khai báo|10349-10357", + "[|[|10358-10359", + "Identifier|first|10359-10364", + ",|,|10364-10365", + "Identifier|second|10366-10372", + ",|,|10372-10373", + "...|...|10374-10377", + "Identifier|others|10377-10383", + "]|]|10383-10384", + "=|=|10385-10386", + "Identifier|arr|10387-10390", + "Var|khai báo|10392-10400", + "Identifier|result|10401-10407", + "=|=|10408-10409", + "Identifier|đếm|10410-10413", + ">|>|10414-10415", + "Number|50|10416-10418", + "?|?|10419-10420", + "String|"lớn"|10421-10426", + ":|:|10427-10428", + "Identifier|đếm|10429-10432", + ">|>|10433-10434", + "Number|10|10435-10437", + "?|?|10438-10439", + "String|"trung bình"|10440-10452", + ":|:|10453-10454", + "String|"nhỏ"|10455-10460", + "Var|khai báo|10462-10470", + "Identifier|greeting|10471-10479", + "=|=|10480-10481", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|10482-10521", + "Var|khai báo|10523-10531", + "Identifier|regex|10532-10537", + "=|=|10538-10539", + "RegExpLiteral|/^[a-z]+$/gi|10540-10552", + "Export|cho phép|10554-10562", + "Default|default|10563-10570", + "Identifier|Hình Tròn|10571-10580", + "Export|cho phép|10581-10589", + "{|{|10590-10591", + "Identifier|Tam Giác|10592-10600", + ",|,|10600-10601", + "Identifier|tính diện tích5|10602-10617", + "}|}|10618-10619", + "Import|sử dụng|10621-10628", + "Identifier|Foo|10629-10632", + "From|từ|10633-10635", + "String|"./foo"|10636-10643", + "Import|sử dụng|10644-10651", + "{|{|10652-10653", + "Identifier|Bar|10654-10657", + ",|,|10657-10658", + "Identifier|Baz|10659-10662", + "As|như là|10663-10669", + "Identifier|QualifiedBaz|10670-10682", + "}|}|10683-10684", + "From|từ|10685-10687", + "String|"./bar"|10688-10695", + "Var|khai báo|10799-10807", + "Identifier|PI|10808-10810", + "=|=|10811-10812", + "Number|3.14159|10813-10820", + "Const|hằng số|10821-10828", + "Identifier|E|10829-10830", + "=|=|10831-10832", + "Number|2.71828|10833-10840", + "Let|biến|10841-10845", + "Identifier|đếm|10846-10849", + "=|=|10850-10851", + "Number|0|10852-10853", + "Function|hàm|10855-10858", + "Identifier|tính diện tích6|10859-10874", + "(|(|10874-10875", + "Identifier|bán kính|10875-10883", + ")|)|10883-10884", + "{|{|10885-10886", + "Return|trả về|10889-10895", + "Identifier|PI|10896-10898", + "*|*|10899-10900", + "Identifier|bán kính|10901-10909", + "*|*|10910-10911", + "Identifier|bán kính|10912-10920", + "}|}|10921-10922", + "Function|hàm|10924-10927", + "Identifier|cộng|10928-10932", + "(|(|10932-10933", + "Identifier|a|10933-10934", + ",|,|10934-10935", + "Identifier|b|10936-10937", + ")|)|10937-10938", + "{|{|10939-10940", + "Return|trả về|10943-10949", + "Identifier|a|10950-10951", + "+|+|10952-10953", + "Identifier|b|10954-10955", + "}|}|10956-10957", + "Const|const|10959-10964", + "Identifier|cube|10965-10969", + "=|=|10970-10971", + "(|(|10972-10973", + "Identifier|x|10973-10974", + ")|)|10974-10975", + "=>|=>|10976-10978", + "Identifier|x|10979-10980", + "**|**|10981-10983", + "Number|3|10984-10985", + "Const|const|10986-10991", + "Identifier|square|10992-10998", + "=|=|10999-11000", + "Function|function|11001-11009", + "(|(|11010-11011", + "Identifier|x|11011-11012", + ")|)|11012-11013", + "{|{|11014-11015", + "Return|return|11016-11022", + "Identifier|x|11023-11024", + "*|*|11025-11026", + "Identifier|x|11027-11028", + "}|}|11029-11030", + "Class|lớp|11032-11035", + "Identifier|Hình Tròn|11036-11045", + "{|{|11046-11047", + "Constructor|khởi tạo|11050-11058", + "(|(|11058-11059", + "Identifier|bán kính|11059-11067", + ")|)|11067-11068", + "{|{|11069-11070", + "This|đây|11075-11078", + ".|.|11078-11079", + "Identifier|bán kính|11079-11087", + "=|=|11088-11089", + "Identifier|bán kính|11090-11098", + "}|}|11101-11102", + "Get|lấy|11106-11109", + "Identifier|diện tích6|11110-11120", + "(|(|11120-11121", + ")|)|11121-11122", + "{|{|11123-11124", + "Return|trả về|11129-11135", + "Identifier|tính diện tích6|11136-11151", + "(|(|11151-11152", + "This|đây|11152-11155", + ".|.|11155-11156", + "Identifier|bán kính|11156-11164", + ")|)|11164-11165", + "}|}|11168-11169", + "Static|tĩnh|11173-11177", + "Identifier|tạo|11178-11181", + "From|từ|11182-11184", + "Identifier|đường kính|11185-11195", + "(|(|11195-11196", + "Identifier|d|11196-11197", + ")|)|11197-11198", + "{|{|11199-11200", + "Return|trả về|11205-11211", + "New|new|11212-11215", + "Identifier|Hình Tròn|11216-11225", + "(|(|11225-11226", + "Identifier|d|11226-11227", + "/|/|11228-11229", + "Number|2|11230-11231", + ")|)|11231-11232", + "}|}|11235-11236", + "}|}|11237-11238", + "Class|lớp|11240-11243", + "Identifier|Tam Giác|11244-11252", + "Extends|kế thừa|11253-11260", + "Identifier|Hình Tròn|11261-11270", + "{|{|11271-11272", + "Private|riêng tư|11275-11283", + "Identifier|cạnh|11284-11288", + "Public|công khai|11291-11300", + "Identifier|tên|11301-11304", + "=|=|11305-11306", + "String|"tam giác"|11307-11317", + "Constructor|khởi tạo|11321-11329", + "(|(|11329-11330", + "Identifier|cạnh|11330-11334", + ")|)|11334-11335", + "{|{|11336-11337", + "Super|khởi tạo cha|11342-11354", + "(|(|11354-11355", + "Identifier|cạnh|11355-11359", + ")|)|11359-11360", + "This|đây|11365-11368", + ".|.|11368-11369", + "Identifier|cạnh|11369-11373", + "=|=|11374-11375", + "Identifier|cạnh|11376-11380", + "}|}|11383-11384", + "}|}|11385-11386", + "For|lặp|11388-11391", + "(|(|11392-11393", + "Var|khai báo|11393-11401", + "Identifier|i|11402-11403", + "=|=|11404-11405", + "Number|0|11406-11407", + ";|;|11407-11408", + "Identifier|i|11409-11410", + "<|<|11411-11412", + "Number|100|11413-11416", + ";|;|11416-11417", + "Identifier|i|11418-11419", + "++|++|11419-11421", + ")|)|11421-11422", + "{|{|11423-11424", + "Identifier|đếm|11427-11430", + "=|=|11431-11432", + "Identifier|đếm|11433-11436", + "+|+|11437-11438", + "Identifier|i|11439-11440", + "If|nếu|11443-11446", + "(|(|11447-11448", + "Identifier|i|11448-11449", + "%|%|11450-11451", + "Number|2|11452-11453", + "===|===|11454-11457", + "Number|0|11458-11459", + ")|)|11459-11460", + "{|{|11461-11462", + "Continue|tiếp tục|11467-11475", + "}|}|11478-11479", + "If|nếu|11482-11485", + "(|(|11486-11487", + "Identifier|i|11487-11488", + ">|>|11489-11490", + "Number|50|11491-11493", + ")|)|11493-11494", + "{|{|11495-11496", + "Break|phá vòng lặp|11501-11513", + "}|}|11516-11517", + "}|}|11518-11519", + "For|lặp|11521-11524", + "(|(|11525-11526", + "Var|khai báo|11526-11534", + "Identifier|phần tử|11535-11542", + "Of|của|11543-11546", + "[|[|11547-11548", + "Number|1|11548-11549", + ",|,|11549-11550", + "Number|2|11551-11552", + ",|,|11552-11553", + "Number|3|11554-11555", + ",|,|11555-11556", + "Number|4|11557-11558", + ",|,|11558-11559", + "Number|5|11560-11561", + "]|]|11561-11562", + ")|)|11562-11563", + "{|{|11564-11565", + "Identifier|console|11568-11575", + ".|.|11575-11576", + "Identifier|log|11576-11579", + "(|(|11579-11580", + "Identifier|phần tử|11580-11587", + ")|)|11587-11588", + "}|}|11589-11590", + "While|khi mà|11592-11598", + "(|(|11599-11600", + "Identifier|đếm|11600-11603", + ">|>|11604-11605", + "Number|0|11606-11607", + ")|)|11607-11608", + "{|{|11609-11610", + "Identifier|đếm|11613-11616", + "--|--|11616-11618", + "}|}|11619-11620", + "Switch|duyệt|11622-11627", + "(|(|11628-11629", + "Identifier|đếm|11629-11632", + ")|)|11632-11633", + "{|{|11634-11635", + "Case|trường hợp|11638-11648", + "Number|0|11649-11650", + ":|:|11650-11651", + "Identifier|console|11656-11663", + ".|.|11663-11664", + "Identifier|log|11664-11667", + "(|(|11667-11668", + "String|"zero"|11668-11674", + ")|)|11674-11675", + "Break|phá vòng lặp|11680-11692", + "Case|trường hợp|11695-11705", + "Number|1|11706-11707", + ":|:|11707-11708", + "Case|trường hợp|11711-11721", + "Number|2|11722-11723", + ":|:|11723-11724", + "Identifier|console|11729-11736", + ".|.|11736-11737", + "Identifier|log|11737-11740", + "(|(|11740-11741", + "String|"nhỏ"|11741-11746", + ")|)|11746-11747", + "Break|phá vòng lặp|11752-11764", + "Default|mặc định|11767-11775", + ":|:|11775-11776", + "Identifier|console|11781-11788", + ".|.|11788-11789", + "Identifier|log|11789-11792", + "(|(|11792-11793", + "String|"khác"|11793-11799", + ")|)|11799-11800", + "}|}|11801-11802", + "Try|thử|11804-11807", + "{|{|11808-11809", + "Var|khai báo|11812-11820", + "Identifier|x|11821-11822", + "=|=|11823-11824", + "Identifier|JSON|11825-11829", + ".|.|11829-11830", + "Identifier|parse|11830-11835", + "(|(|11835-11836", + "String|'{"a":1}'|11836-11845", + ")|)|11845-11846", + "}|}|11847-11848", + "Catch|bắt lỗi|11849-11856", + "(|(|11857-11858", + "Identifier|lỗi|11858-11861", + ")|)|11861-11862", + "{|{|11863-11864", + "Identifier|console|11867-11874", + ".|.|11874-11875", + "Identifier|error|11875-11880", + "(|(|11880-11881", + "Identifier|lỗi|11881-11884", + ")|)|11884-11885", + "}|}|11886-11887", + "Finally|cuối cùng|11888-11897", + "{|{|11898-11899", + "Identifier|console|11902-11909", + ".|.|11909-11910", + "Identifier|log|11910-11913", + "(|(|11913-11914", + "String|"xong"|11914-11920", + ")|)|11920-11921", + "}|}|11922-11923", + "Async|bất đồng bộ|11925-11936", + "Function|hàm|11937-11940", + "Identifier|fetchData|11941-11950", + "(|(|11950-11951", + "Identifier|url|11951-11954", + ")|)|11954-11955", + "{|{|11956-11957", + "Var|khai báo|11960-11968", + "Identifier|res|11969-11972", + "=|=|11973-11974", + "Await|chờ|11975-11978", + "Identifier|fetch|11979-11984", + "(|(|11984-11985", + "Identifier|url|11985-11988", + ")|)|11988-11989", + "Return|trả về|11992-11998", + "Await|chờ|11999-12002", + "Identifier|res|12003-12006", + ".|.|12006-12007", + "Identifier|json|12007-12011", + "(|(|12011-12012", + ")|)|12012-12013", + "}|}|12014-12015", + "Var|khai báo|12017-12025", + "Identifier|arr|12026-12029", + "=|=|12030-12031", + "[|[|12032-12033", + "Number|1|12033-12034", + ",|,|12034-12035", + "Number|2|12036-12037", + ",|,|12037-12038", + "Number|3|12039-12040", + ",|,|12040-12041", + "...|...|12042-12045", + "[|[|12045-12046", + "Number|4|12046-12047", + ",|,|12047-12048", + "Number|5|12049-12050", + ",|,|12050-12051", + "Number|6|12052-12053", + "]|]|12053-12054", + "]|]|12054-12055", + "Var|khai báo|12056-12064", + "Identifier|obj|12065-12068", + "=|=|12069-12070", + "{|{|12071-12072", + "Identifier|a|12073-12074", + ":|:|12074-12075", + "Number|1|12076-12077", + ",|,|12077-12078", + "Identifier|b|12079-12080", + ":|:|12080-12081", + "Number|2|12082-12083", + ",|,|12083-12084", + "Identifier|c|12085-12086", + ":|:|12086-12087", + "{|{|12088-12089", + "Identifier|d|12090-12091", + ":|:|12091-12092", + "Number|3|12093-12094", + "}|}|12095-12096", + "}|}|12097-12098", + "Var|khai báo|12099-12107", + "{|{|12108-12109", + "Identifier|a|12110-12111", + ",|,|12111-12112", + "Identifier|b|12113-12114", + ",|,|12114-12115", + "...|...|12116-12119", + "Identifier|rest|12119-12123", + "}|}|12124-12125", + "=|=|12126-12127", + "Identifier|obj|12128-12131", + "Var|khai báo|12132-12140", + "[|[|12141-12142", + "Identifier|first|12142-12147", + ",|,|12147-12148", + "Identifier|second|12149-12155", + ",|,|12155-12156", + "...|...|12157-12160", + "Identifier|others|12160-12166", + "]|]|12166-12167", + "=|=|12168-12169", + "Identifier|arr|12170-12173", + "Var|khai báo|12175-12183", + "Identifier|result|12184-12190", + "=|=|12191-12192", + "Identifier|đếm|12193-12196", + ">|>|12197-12198", + "Number|50|12199-12201", + "?|?|12202-12203", + "String|"lớn"|12204-12209", + ":|:|12210-12211", + "Identifier|đếm|12212-12215", + ">|>|12216-12217", + "Number|10|12218-12220", + "?|?|12221-12222", + "String|"trung bình"|12223-12235", + ":|:|12236-12237", + "String|"nhỏ"|12238-12243", + "Var|khai báo|12245-12253", + "Identifier|greeting|12254-12262", + "=|=|12263-12264", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|12265-12304", + "Var|khai báo|12306-12314", + "Identifier|regex|12315-12320", + "=|=|12321-12322", + "RegExpLiteral|/^[a-z]+$/gi|12323-12335", + "Export|cho phép|12337-12345", + "Default|default|12346-12353", + "Identifier|Hình Tròn|12354-12363", + "Export|cho phép|12364-12372", + "{|{|12373-12374", + "Identifier|Tam Giác|12375-12383", + ",|,|12383-12384", + "Identifier|tính diện tích6|12385-12400", + "}|}|12401-12402", + "Import|sử dụng|12404-12411", + "Identifier|Foo|12412-12415", + "From|từ|12416-12418", + "String|"./foo"|12419-12426", + "Import|sử dụng|12427-12434", + "{|{|12435-12436", + "Identifier|Bar|12437-12440", + ",|,|12440-12441", + "Identifier|Baz|12442-12445", + "As|như là|12446-12452", + "Identifier|QualifiedBaz|12453-12465", + "}|}|12466-12467", + "From|từ|12468-12470", + "String|"./bar"|12471-12478", + "Var|khai báo|12582-12590", + "Identifier|PI|12591-12593", + "=|=|12594-12595", + "Number|3.14159|12596-12603", + "Const|hằng số|12604-12611", + "Identifier|E|12612-12613", + "=|=|12614-12615", + "Number|2.71828|12616-12623", + "Let|biến|12624-12628", + "Identifier|đếm|12629-12632", + "=|=|12633-12634", + "Number|0|12635-12636", + "Function|hàm|12638-12641", + "Identifier|tính diện tích7|12642-12657", + "(|(|12657-12658", + "Identifier|bán kính|12658-12666", + ")|)|12666-12667", + "{|{|12668-12669", + "Return|trả về|12672-12678", + "Identifier|PI|12679-12681", + "*|*|12682-12683", + "Identifier|bán kính|12684-12692", + "*|*|12693-12694", + "Identifier|bán kính|12695-12703", + "}|}|12704-12705", + "Function|hàm|12707-12710", + "Identifier|cộng|12711-12715", + "(|(|12715-12716", + "Identifier|a|12716-12717", + ",|,|12717-12718", + "Identifier|b|12719-12720", + ")|)|12720-12721", + "{|{|12722-12723", + "Return|trả về|12726-12732", + "Identifier|a|12733-12734", + "+|+|12735-12736", + "Identifier|b|12737-12738", + "}|}|12739-12740", + "Const|const|12742-12747", + "Identifier|cube|12748-12752", + "=|=|12753-12754", + "(|(|12755-12756", + "Identifier|x|12756-12757", + ")|)|12757-12758", + "=>|=>|12759-12761", + "Identifier|x|12762-12763", + "**|**|12764-12766", + "Number|3|12767-12768", + "Const|const|12769-12774", + "Identifier|square|12775-12781", + "=|=|12782-12783", + "Function|function|12784-12792", + "(|(|12793-12794", + "Identifier|x|12794-12795", + ")|)|12795-12796", + "{|{|12797-12798", + "Return|return|12799-12805", + "Identifier|x|12806-12807", + "*|*|12808-12809", + "Identifier|x|12810-12811", + "}|}|12812-12813", + "Class|lớp|12815-12818", + "Identifier|Hình Tròn|12819-12828", + "{|{|12829-12830", + "Constructor|khởi tạo|12833-12841", + "(|(|12841-12842", + "Identifier|bán kính|12842-12850", + ")|)|12850-12851", + "{|{|12852-12853", + "This|đây|12858-12861", + ".|.|12861-12862", + "Identifier|bán kính|12862-12870", + "=|=|12871-12872", + "Identifier|bán kính|12873-12881", + "}|}|12884-12885", + "Get|lấy|12889-12892", + "Identifier|diện tích7|12893-12903", + "(|(|12903-12904", + ")|)|12904-12905", + "{|{|12906-12907", + "Return|trả về|12912-12918", + "Identifier|tính diện tích7|12919-12934", + "(|(|12934-12935", + "This|đây|12935-12938", + ".|.|12938-12939", + "Identifier|bán kính|12939-12947", + ")|)|12947-12948", + "}|}|12951-12952", + "Static|tĩnh|12956-12960", + "Identifier|tạo|12961-12964", + "From|từ|12965-12967", + "Identifier|đường kính|12968-12978", + "(|(|12978-12979", + "Identifier|d|12979-12980", + ")|)|12980-12981", + "{|{|12982-12983", + "Return|trả về|12988-12994", + "New|new|12995-12998", + "Identifier|Hình Tròn|12999-13008", + "(|(|13008-13009", + "Identifier|d|13009-13010", + "/|/|13011-13012", + "Number|2|13013-13014", + ")|)|13014-13015", + "}|}|13018-13019", + "}|}|13020-13021", + "Class|lớp|13023-13026", + "Identifier|Tam Giác|13027-13035", + "Extends|kế thừa|13036-13043", + "Identifier|Hình Tròn|13044-13053", + "{|{|13054-13055", + "Private|riêng tư|13058-13066", + "Identifier|cạnh|13067-13071", + "Public|công khai|13074-13083", + "Identifier|tên|13084-13087", + "=|=|13088-13089", + "String|"tam giác"|13090-13100", + "Constructor|khởi tạo|13104-13112", + "(|(|13112-13113", + "Identifier|cạnh|13113-13117", + ")|)|13117-13118", + "{|{|13119-13120", + "Super|khởi tạo cha|13125-13137", + "(|(|13137-13138", + "Identifier|cạnh|13138-13142", + ")|)|13142-13143", + "This|đây|13148-13151", + ".|.|13151-13152", + "Identifier|cạnh|13152-13156", + "=|=|13157-13158", + "Identifier|cạnh|13159-13163", + "}|}|13166-13167", + "}|}|13168-13169", + "For|lặp|13171-13174", + "(|(|13175-13176", + "Var|khai báo|13176-13184", + "Identifier|i|13185-13186", + "=|=|13187-13188", + "Number|0|13189-13190", + ";|;|13190-13191", + "Identifier|i|13192-13193", + "<|<|13194-13195", + "Number|100|13196-13199", + ";|;|13199-13200", + "Identifier|i|13201-13202", + "++|++|13202-13204", + ")|)|13204-13205", + "{|{|13206-13207", + "Identifier|đếm|13210-13213", + "=|=|13214-13215", + "Identifier|đếm|13216-13219", + "+|+|13220-13221", + "Identifier|i|13222-13223", + "If|nếu|13226-13229", + "(|(|13230-13231", + "Identifier|i|13231-13232", + "%|%|13233-13234", + "Number|2|13235-13236", + "===|===|13237-13240", + "Number|0|13241-13242", + ")|)|13242-13243", + "{|{|13244-13245", + "Continue|tiếp tục|13250-13258", + "}|}|13261-13262", + "If|nếu|13265-13268", + "(|(|13269-13270", + "Identifier|i|13270-13271", + ">|>|13272-13273", + "Number|50|13274-13276", + ")|)|13276-13277", + "{|{|13278-13279", + "Break|phá vòng lặp|13284-13296", + "}|}|13299-13300", + "}|}|13301-13302", + "For|lặp|13304-13307", + "(|(|13308-13309", + "Var|khai báo|13309-13317", + "Identifier|phần tử|13318-13325", + "Of|của|13326-13329", + "[|[|13330-13331", + "Number|1|13331-13332", + ",|,|13332-13333", + "Number|2|13334-13335", + ",|,|13335-13336", + "Number|3|13337-13338", + ",|,|13338-13339", + "Number|4|13340-13341", + ",|,|13341-13342", + "Number|5|13343-13344", + "]|]|13344-13345", + ")|)|13345-13346", + "{|{|13347-13348", + "Identifier|console|13351-13358", + ".|.|13358-13359", + "Identifier|log|13359-13362", + "(|(|13362-13363", + "Identifier|phần tử|13363-13370", + ")|)|13370-13371", + "}|}|13372-13373", + "While|khi mà|13375-13381", + "(|(|13382-13383", + "Identifier|đếm|13383-13386", + ">|>|13387-13388", + "Number|0|13389-13390", + ")|)|13390-13391", + "{|{|13392-13393", + "Identifier|đếm|13396-13399", + "--|--|13399-13401", + "}|}|13402-13403", + "Switch|duyệt|13405-13410", + "(|(|13411-13412", + "Identifier|đếm|13412-13415", + ")|)|13415-13416", + "{|{|13417-13418", + "Case|trường hợp|13421-13431", + "Number|0|13432-13433", + ":|:|13433-13434", + "Identifier|console|13439-13446", + ".|.|13446-13447", + "Identifier|log|13447-13450", + "(|(|13450-13451", + "String|"zero"|13451-13457", + ")|)|13457-13458", + "Break|phá vòng lặp|13463-13475", + "Case|trường hợp|13478-13488", + "Number|1|13489-13490", + ":|:|13490-13491", + "Case|trường hợp|13494-13504", + "Number|2|13505-13506", + ":|:|13506-13507", + "Identifier|console|13512-13519", + ".|.|13519-13520", + "Identifier|log|13520-13523", + "(|(|13523-13524", + "String|"nhỏ"|13524-13529", + ")|)|13529-13530", + "Break|phá vòng lặp|13535-13547", + "Default|mặc định|13550-13558", + ":|:|13558-13559", + "Identifier|console|13564-13571", + ".|.|13571-13572", + "Identifier|log|13572-13575", + "(|(|13575-13576", + "String|"khác"|13576-13582", + ")|)|13582-13583", + "}|}|13584-13585", + "Try|thử|13587-13590", + "{|{|13591-13592", + "Var|khai báo|13595-13603", + "Identifier|x|13604-13605", + "=|=|13606-13607", + "Identifier|JSON|13608-13612", + ".|.|13612-13613", + "Identifier|parse|13613-13618", + "(|(|13618-13619", + "String|'{"a":1}'|13619-13628", + ")|)|13628-13629", + "}|}|13630-13631", + "Catch|bắt lỗi|13632-13639", + "(|(|13640-13641", + "Identifier|lỗi|13641-13644", + ")|)|13644-13645", + "{|{|13646-13647", + "Identifier|console|13650-13657", + ".|.|13657-13658", + "Identifier|error|13658-13663", + "(|(|13663-13664", + "Identifier|lỗi|13664-13667", + ")|)|13667-13668", + "}|}|13669-13670", + "Finally|cuối cùng|13671-13680", + "{|{|13681-13682", + "Identifier|console|13685-13692", + ".|.|13692-13693", + "Identifier|log|13693-13696", + "(|(|13696-13697", + "String|"xong"|13697-13703", + ")|)|13703-13704", + "}|}|13705-13706", + "Async|bất đồng bộ|13708-13719", + "Function|hàm|13720-13723", + "Identifier|fetchData|13724-13733", + "(|(|13733-13734", + "Identifier|url|13734-13737", + ")|)|13737-13738", + "{|{|13739-13740", + "Var|khai báo|13743-13751", + "Identifier|res|13752-13755", + "=|=|13756-13757", + "Await|chờ|13758-13761", + "Identifier|fetch|13762-13767", + "(|(|13767-13768", + "Identifier|url|13768-13771", + ")|)|13771-13772", + "Return|trả về|13775-13781", + "Await|chờ|13782-13785", + "Identifier|res|13786-13789", + ".|.|13789-13790", + "Identifier|json|13790-13794", + "(|(|13794-13795", + ")|)|13795-13796", + "}|}|13797-13798", + "Var|khai báo|13800-13808", + "Identifier|arr|13809-13812", + "=|=|13813-13814", + "[|[|13815-13816", + "Number|1|13816-13817", + ",|,|13817-13818", + "Number|2|13819-13820", + ",|,|13820-13821", + "Number|3|13822-13823", + ",|,|13823-13824", + "...|...|13825-13828", + "[|[|13828-13829", + "Number|4|13829-13830", + ",|,|13830-13831", + "Number|5|13832-13833", + ",|,|13833-13834", + "Number|6|13835-13836", + "]|]|13836-13837", + "]|]|13837-13838", + "Var|khai báo|13839-13847", + "Identifier|obj|13848-13851", + "=|=|13852-13853", + "{|{|13854-13855", + "Identifier|a|13856-13857", + ":|:|13857-13858", + "Number|1|13859-13860", + ",|,|13860-13861", + "Identifier|b|13862-13863", + ":|:|13863-13864", + "Number|2|13865-13866", + ",|,|13866-13867", + "Identifier|c|13868-13869", + ":|:|13869-13870", + "{|{|13871-13872", + "Identifier|d|13873-13874", + ":|:|13874-13875", + "Number|3|13876-13877", + "}|}|13878-13879", + "}|}|13880-13881", + "Var|khai báo|13882-13890", + "{|{|13891-13892", + "Identifier|a|13893-13894", + ",|,|13894-13895", + "Identifier|b|13896-13897", + ",|,|13897-13898", + "...|...|13899-13902", + "Identifier|rest|13902-13906", + "}|}|13907-13908", + "=|=|13909-13910", + "Identifier|obj|13911-13914", + "Var|khai báo|13915-13923", + "[|[|13924-13925", + "Identifier|first|13925-13930", + ",|,|13930-13931", + "Identifier|second|13932-13938", + ",|,|13938-13939", + "...|...|13940-13943", + "Identifier|others|13943-13949", + "]|]|13949-13950", + "=|=|13951-13952", + "Identifier|arr|13953-13956", + "Var|khai báo|13958-13966", + "Identifier|result|13967-13973", + "=|=|13974-13975", + "Identifier|đếm|13976-13979", + ">|>|13980-13981", + "Number|50|13982-13984", + "?|?|13985-13986", + "String|"lớn"|13987-13992", + ":|:|13993-13994", + "Identifier|đếm|13995-13998", + ">|>|13999-14000", + "Number|10|14001-14003", + "?|?|14004-14005", + "String|"trung bình"|14006-14018", + ":|:|14019-14020", + "String|"nhỏ"|14021-14026", + "Var|khai báo|14028-14036", + "Identifier|greeting|14037-14045", + "=|=|14046-14047", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|14048-14087", + "Var|khai báo|14089-14097", + "Identifier|regex|14098-14103", + "=|=|14104-14105", + "RegExpLiteral|/^[a-z]+$/gi|14106-14118", + "Export|cho phép|14120-14128", + "Default|default|14129-14136", + "Identifier|Hình Tròn|14137-14146", + "Export|cho phép|14147-14155", + "{|{|14156-14157", + "Identifier|Tam Giác|14158-14166", + ",|,|14166-14167", + "Identifier|tính diện tích7|14168-14183", + "}|}|14184-14185", + "Import|sử dụng|14187-14194", + "Identifier|Foo|14195-14198", + "From|từ|14199-14201", + "String|"./foo"|14202-14209", + "Import|sử dụng|14210-14217", + "{|{|14218-14219", + "Identifier|Bar|14220-14223", + ",|,|14223-14224", + "Identifier|Baz|14225-14228", + "As|như là|14229-14235", + "Identifier|QualifiedBaz|14236-14248", + "}|}|14249-14250", + "From|từ|14251-14253", + "String|"./bar"|14254-14261", +] +`; + +exports[`bench fixtures token-stream snapshots > stable snapshot: medium 1`] = ` +[ + "Var|khai báo|101-109", + "Identifier|PI|110-112", + "=|=|113-114", + "Number|3.14159|115-122", + "Const|hằng số|123-130", + "Identifier|E|131-132", + "=|=|133-134", + "Number|2.71828|135-142", + "Let|biến|143-147", + "Identifier|đếm|148-151", + "=|=|152-153", + "Number|0|154-155", + "Function|hàm|157-160", + "Identifier|tính diện tích|161-175", + "(|(|175-176", + "Identifier|bán kính|176-184", + ")|)|184-185", + "{|{|186-187", + "Return|trả về|190-196", + "Identifier|PI|197-199", + "*|*|200-201", + "Identifier|bán kính|202-210", + "*|*|211-212", + "Identifier|bán kính|213-221", + "}|}|222-223", + "Function|hàm|225-228", + "Identifier|cộng|229-233", + "(|(|233-234", + "Identifier|a|234-235", + ",|,|235-236", + "Identifier|b|237-238", + ")|)|238-239", + "{|{|240-241", + "Return|trả về|244-250", + "Identifier|a|251-252", + "+|+|253-254", + "Identifier|b|255-256", + "}|}|257-258", + "Const|const|260-265", + "Identifier|cube|266-270", + "=|=|271-272", + "(|(|273-274", + "Identifier|x|274-275", + ")|)|275-276", + "=>|=>|277-279", + "Identifier|x|280-281", + "**|**|282-284", + "Number|3|285-286", + "Const|const|287-292", + "Identifier|square|293-299", + "=|=|300-301", + "Function|function|302-310", + "(|(|311-312", + "Identifier|x|312-313", + ")|)|313-314", + "{|{|315-316", + "Return|return|317-323", + "Identifier|x|324-325", + "*|*|326-327", + "Identifier|x|328-329", + "}|}|330-331", + "Class|lớp|333-336", + "Identifier|Hình Tròn|337-346", + "{|{|347-348", + "Constructor|khởi tạo|351-359", + "(|(|359-360", + "Identifier|bán kính|360-368", + ")|)|368-369", + "{|{|370-371", + "This|đây|376-379", + ".|.|379-380", + "Identifier|bán kính|380-388", + "=|=|389-390", + "Identifier|bán kính|391-399", + "}|}|402-403", + "Get|lấy|407-410", + "Identifier|diện tích|411-420", + "(|(|420-421", + ")|)|421-422", + "{|{|423-424", + "Return|trả về|429-435", + "Identifier|tính diện tích|436-450", + "(|(|450-451", + "This|đây|451-454", + ".|.|454-455", + "Identifier|bán kính|455-463", + ")|)|463-464", + "}|}|467-468", + "Static|tĩnh|472-476", + "Identifier|tạo|477-480", + "From|từ|481-483", + "Identifier|đường kính|484-494", + "(|(|494-495", + "Identifier|d|495-496", + ")|)|496-497", + "{|{|498-499", + "Return|trả về|504-510", + "New|new|511-514", + "Identifier|Hình Tròn|515-524", + "(|(|524-525", + "Identifier|d|525-526", + "/|/|527-528", + "Number|2|529-530", + ")|)|530-531", + "}|}|534-535", + "}|}|536-537", + "Class|lớp|539-542", + "Identifier|Tam Giác|543-551", + "Extends|kế thừa|552-559", + "Identifier|Hình Tròn|560-569", + "{|{|570-571", + "Private|riêng tư|574-582", + "Identifier|cạnh|583-587", + "Public|công khai|590-599", + "Identifier|tên|600-603", + "=|=|604-605", + "String|"tam giác"|606-616", + "Constructor|khởi tạo|620-628", + "(|(|628-629", + "Identifier|cạnh|629-633", + ")|)|633-634", + "{|{|635-636", + "Super|khởi tạo cha|641-653", + "(|(|653-654", + "Identifier|cạnh|654-658", + ")|)|658-659", + "This|đây|664-667", + ".|.|667-668", + "Identifier|cạnh|668-672", + "=|=|673-674", + "Identifier|cạnh|675-679", + "}|}|682-683", + "}|}|684-685", + "For|lặp|687-690", + "(|(|691-692", + "Var|khai báo|692-700", + "Identifier|i|701-702", + "=|=|703-704", + "Number|0|705-706", + ";|;|706-707", + "Identifier|i|708-709", + "<|<|710-711", + "Number|100|712-715", + ";|;|715-716", + "Identifier|i|717-718", + "++|++|718-720", + ")|)|720-721", + "{|{|722-723", + "Identifier|đếm|726-729", + "=|=|730-731", + "Identifier|đếm|732-735", + "+|+|736-737", + "Identifier|i|738-739", + "If|nếu|742-745", + "(|(|746-747", + "Identifier|i|747-748", + "%|%|749-750", + "Number|2|751-752", + "===|===|753-756", + "Number|0|757-758", + ")|)|758-759", + "{|{|760-761", + "Continue|tiếp tục|766-774", + "}|}|777-778", + "If|nếu|781-784", + "(|(|785-786", + "Identifier|i|786-787", + ">|>|788-789", + "Number|50|790-792", + ")|)|792-793", + "{|{|794-795", + "Break|phá vòng lặp|800-812", + "}|}|815-816", + "}|}|817-818", + "For|lặp|820-823", + "(|(|824-825", + "Var|khai báo|825-833", + "Identifier|phần tử|834-841", + "Of|của|842-845", + "[|[|846-847", + "Number|1|847-848", + ",|,|848-849", + "Number|2|850-851", + ",|,|851-852", + "Number|3|853-854", + ",|,|854-855", + "Number|4|856-857", + ",|,|857-858", + "Number|5|859-860", + "]|]|860-861", + ")|)|861-862", + "{|{|863-864", + "Identifier|console|867-874", + ".|.|874-875", + "Identifier|log|875-878", + "(|(|878-879", + "Identifier|phần tử|879-886", + ")|)|886-887", + "}|}|888-889", + "While|khi mà|891-897", + "(|(|898-899", + "Identifier|đếm|899-902", + ">|>|903-904", + "Number|0|905-906", + ")|)|906-907", + "{|{|908-909", + "Identifier|đếm|912-915", + "--|--|915-917", + "}|}|918-919", + "Switch|duyệt|921-926", + "(|(|927-928", + "Identifier|đếm|928-931", + ")|)|931-932", + "{|{|933-934", + "Case|trường hợp|937-947", + "Number|0|948-949", + ":|:|949-950", + "Identifier|console|955-962", + ".|.|962-963", + "Identifier|log|963-966", + "(|(|966-967", + "String|"zero"|967-973", + ")|)|973-974", + "Break|phá vòng lặp|979-991", + "Case|trường hợp|994-1004", + "Number|1|1005-1006", + ":|:|1006-1007", + "Case|trường hợp|1010-1020", + "Number|2|1021-1022", + ":|:|1022-1023", + "Identifier|console|1028-1035", + ".|.|1035-1036", + "Identifier|log|1036-1039", + "(|(|1039-1040", + "String|"nhỏ"|1040-1045", + ")|)|1045-1046", + "Break|phá vòng lặp|1051-1063", + "Default|mặc định|1066-1074", + ":|:|1074-1075", + "Identifier|console|1080-1087", + ".|.|1087-1088", + "Identifier|log|1088-1091", + "(|(|1091-1092", + "String|"khác"|1092-1098", + ")|)|1098-1099", + "}|}|1100-1101", + "Try|thử|1103-1106", + "{|{|1107-1108", + "Var|khai báo|1111-1119", + "Identifier|x|1120-1121", + "=|=|1122-1123", + "Identifier|JSON|1124-1128", + ".|.|1128-1129", + "Identifier|parse|1129-1134", + "(|(|1134-1135", + "String|'{"a":1}'|1135-1144", + ")|)|1144-1145", + "}|}|1146-1147", + "Catch|bắt lỗi|1148-1155", + "(|(|1156-1157", + "Identifier|lỗi|1157-1160", + ")|)|1160-1161", + "{|{|1162-1163", + "Identifier|console|1166-1173", + ".|.|1173-1174", + "Identifier|error|1174-1179", + "(|(|1179-1180", + "Identifier|lỗi|1180-1183", + ")|)|1183-1184", + "}|}|1185-1186", + "Finally|cuối cùng|1187-1196", + "{|{|1197-1198", + "Identifier|console|1201-1208", + ".|.|1208-1209", + "Identifier|log|1209-1212", + "(|(|1212-1213", + "String|"xong"|1213-1219", + ")|)|1219-1220", + "}|}|1221-1222", + "Async|bất đồng bộ|1224-1235", + "Function|hàm|1236-1239", + "Identifier|fetchData|1240-1249", + "(|(|1249-1250", + "Identifier|url|1250-1253", + ")|)|1253-1254", + "{|{|1255-1256", + "Var|khai báo|1259-1267", + "Identifier|res|1268-1271", + "=|=|1272-1273", + "Await|chờ|1274-1277", + "Identifier|fetch|1278-1283", + "(|(|1283-1284", + "Identifier|url|1284-1287", + ")|)|1287-1288", + "Return|trả về|1291-1297", + "Await|chờ|1298-1301", + "Identifier|res|1302-1305", + ".|.|1305-1306", + "Identifier|json|1306-1310", + "(|(|1310-1311", + ")|)|1311-1312", + "}|}|1313-1314", + "Var|khai báo|1316-1324", + "Identifier|arr|1325-1328", + "=|=|1329-1330", + "[|[|1331-1332", + "Number|1|1332-1333", + ",|,|1333-1334", + "Number|2|1335-1336", + ",|,|1336-1337", + "Number|3|1338-1339", + ",|,|1339-1340", + "...|...|1341-1344", + "[|[|1344-1345", + "Number|4|1345-1346", + ",|,|1346-1347", + "Number|5|1348-1349", + ",|,|1349-1350", + "Number|6|1351-1352", + "]|]|1352-1353", + "]|]|1353-1354", + "Var|khai báo|1355-1363", + "Identifier|obj|1364-1367", + "=|=|1368-1369", + "{|{|1370-1371", + "Identifier|a|1372-1373", + ":|:|1373-1374", + "Number|1|1375-1376", + ",|,|1376-1377", + "Identifier|b|1378-1379", + ":|:|1379-1380", + "Number|2|1381-1382", + ",|,|1382-1383", + "Identifier|c|1384-1385", + ":|:|1385-1386", + "{|{|1387-1388", + "Identifier|d|1389-1390", + ":|:|1390-1391", + "Number|3|1392-1393", + "}|}|1394-1395", + "}|}|1396-1397", + "Var|khai báo|1398-1406", + "{|{|1407-1408", + "Identifier|a|1409-1410", + ",|,|1410-1411", + "Identifier|b|1412-1413", + ",|,|1413-1414", + "...|...|1415-1418", + "Identifier|rest|1418-1422", + "}|}|1423-1424", + "=|=|1425-1426", + "Identifier|obj|1427-1430", + "Var|khai báo|1431-1439", + "[|[|1440-1441", + "Identifier|first|1441-1446", + ",|,|1446-1447", + "Identifier|second|1448-1454", + ",|,|1454-1455", + "...|...|1456-1459", + "Identifier|others|1459-1465", + "]|]|1465-1466", + "=|=|1467-1468", + "Identifier|arr|1469-1472", + "Var|khai báo|1474-1482", + "Identifier|result|1483-1489", + "=|=|1490-1491", + "Identifier|đếm|1492-1495", + ">|>|1496-1497", + "Number|50|1498-1500", + "?|?|1501-1502", + "String|"lớn"|1503-1508", + ":|:|1509-1510", + "Identifier|đếm|1511-1514", + ">|>|1515-1516", + "Number|10|1517-1519", + "?|?|1520-1521", + "String|"trung bình"|1522-1534", + ":|:|1535-1536", + "String|"nhỏ"|1537-1542", + "Var|khai báo|1544-1552", + "Identifier|greeting|1553-1561", + "=|=|1562-1563", + "TemplateLiteral|\`Xin chào \${tên}, bạn đang \${đếm} tuổi\`|1564-1603", + "Var|khai báo|1605-1613", + "Identifier|regex|1614-1619", + "=|=|1620-1621", + "RegExpLiteral|/^[a-z]+$/gi|1622-1634", + "Export|cho phép|1636-1644", + "Default|default|1645-1652", + "Identifier|Hình Tròn|1653-1662", + "Export|cho phép|1663-1671", + "{|{|1672-1673", + "Identifier|Tam Giác|1674-1682", + ",|,|1682-1683", + "Identifier|tính diện tích|1684-1698", + "}|}|1699-1700", + "Import|sử dụng|1702-1709", + "Identifier|Foo|1710-1713", + "From|từ|1714-1716", + "String|"./foo"|1717-1724", + "Import|sử dụng|1725-1732", + "{|{|1733-1734", + "Identifier|Bar|1735-1738", + ",|,|1738-1739", + "Identifier|Baz|1740-1743", + "As|như là|1744-1750", + "Identifier|QualifiedBaz|1751-1763", + "}|}|1764-1765", + "From|từ|1766-1768", + "String|"./bar"|1769-1776", +] +`; + +exports[`bench fixtures token-stream snapshots > stable snapshot: stringHeavy 1`] = ` +[ + "Var|khai báo|55-63", + "Identifier|a|64-65", + "=|=|66-67", + "String|"chuỗi đơn giản"|68-84", + "Var|khai báo|85-93", + "Identifier|b|94-95", + "=|=|96-97", + "String|'chuỗi bằng nháy đơn'|98-119", + "Var|khai báo|120-128", + "Identifier|c|129-130", + "=|=|131-132", + "String|"chứa \\"escape\\" và \\\\backslash"|133-165", + "Var|khai báo|166-174", + "Identifier|d|175-176", + "=|=|177-178", + "String|"unicode á ắ"|179-192", + "Var|khai báo|193-201", + "Identifier|e|202-203", + "=|=|204-205", + "String|"\\n\\t\\r dòng mới"|206-223", + "Var|khai báo|225-233", + "Identifier|t1|234-236", + "=|=|237-238", + "TemplateLiteral|\`template thường\`|239-256", + "Var|khai báo|257-265", + "Identifier|t2|266-268", + "=|=|269-270", + "TemplateLiteral|\`xen kẽ \${a} và \${b}\`|271-292", + "Var|khai báo|293-301", + "Identifier|t3|302-304", + "=|=|305-306", + "TemplateLiteral|\`lồng \${\`bên trong \${c} thêm\`} ngoài\`|307-344", + "Var|khai báo|345-353", + "Identifier|t4|354-356", + "=|=|357-358", + "TemplateLiteral|\`nhiều dòng +qua nhiều +khúc khác nhau \${1 + 2}\`|359-405", + "Var|khai báo|407-415", + "Identifier|t5|416-418", + "=|=|419-420", + "TemplateLiteral|\`phức tạp \${{ a: 1, b: [2, 3] }} thêm\`|421-459", + "Var|khai báo|461-469", + "Identifier|r1|470-472", + "=|=|473-474", + "RegExpLiteral|/^abc$/|475-482", + "Var|khai báo|483-491", + "Identifier|r2|492-494", + "=|=|495-496", + "RegExpLiteral|/[a-zA-Z0-9_-]+/gi|497-515", + "Var|khai báo|516-524", + "Identifier|r3|525-527", + "=|=|528-529", + "RegExpLiteral|/\\d+\\.\\d+/|530-540", + "Var|khai báo|541-549", + "Identifier|r4|550-552", + "=|=|553-554", + "RegExpLiteral|/(?:foo|bar)\\b/i|555-571", + "Var|khai báo|573-581", + "Identifier|divisor|582-589", + "=|=|590-591", + "Identifier|a|592-593", + "/|/|594-595", + "Identifier|b|596-597", + "/|/|598-599", + "Identifier|c|600-601", + "Var|khai báo|602-610", + "Identifier|sum|611-614", + "=|=|615-616", + "(|(|617-618", + "Identifier|a|618-619", + "+|+|620-621", + "Identifier|b|622-623", + ")|)|623-624", + "/|/|625-626", + "Identifier|c|627-628", + "Var|khai báo|630-638", + "Identifier|bigText|639-646", + "=|=|647-648", + "String|"lorem ipsum dolor sit amet, consectetur adipiscing elit. "|649-708", + "+|+|709-710", + "String|"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "|713-782", + "+|+|783-784", + "String|"ut enim ad minim veniam, quis nostrud exercitation ullamco laboris "|787-856", + "+|+|857-858", + "String|"nisi ut aliquip ex ea commodo consequat. duis aute irure dolor in "|861-929", + "+|+|930-931", + "String|"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "|934-1004", + "+|+|1005-1006", + "String|"pariatur. excepteur sint occaecat cupidatat non proident, sunt in culpa "|1009-1083", + "+|+|1084-1085", + "String|"qui officia deserunt mollit anim id est laborum."|1088-1138", + "Var|khai báo|1140-1148", + "Identifier|arr|1149-1152", + "=|=|1153-1154", + "[|[|1155-1156", + "String|"alpha"|1159-1166", + ",|,|1166-1167", + "String|"bravo"|1168-1175", + ",|,|1175-1176", + "String|"charlie"|1177-1186", + ",|,|1186-1187", + "String|"delta"|1188-1195", + ",|,|1195-1196", + "String|"echo"|1197-1203", + ",|,|1203-1204", + "String|"foxtrot"|1207-1216", + ",|,|1216-1217", + "String|"golf"|1218-1224", + ",|,|1224-1225", + "String|"hotel"|1226-1233", + ",|,|1233-1234", + "String|"india"|1235-1242", + ",|,|1242-1243", + "String|"juliet"|1244-1252", + ",|,|1252-1253", + "String|"kilo"|1256-1262", + ",|,|1262-1263", + "String|"lima"|1264-1270", + ",|,|1270-1271", + "String|"mike"|1272-1278", + ",|,|1278-1279", + "String|"november"|1280-1290", + ",|,|1290-1291", + "String|"oscar"|1292-1299", + ",|,|1299-1300", + "String|"papa"|1303-1309", + ",|,|1309-1310", + "String|"quebec"|1311-1319", + ",|,|1319-1320", + "String|"romeo"|1321-1328", + ",|,|1328-1329", + "String|"sierra"|1330-1338", + ",|,|1338-1339", + "String|"tango"|1340-1347", + ",|,|1347-1348", + "String|"uniform"|1351-1360", + ",|,|1360-1361", + "String|"victor"|1362-1370", + ",|,|1370-1371", + "String|"whiskey"|1372-1381", + ",|,|1381-1382", + "String|"xray"|1383-1389", + ",|,|1389-1390", + "String|"yankee"|1391-1399", + ",|,|1399-1400", + "String|"zulu"|1401-1407", + "]|]|1408-1409", +] +`; + +exports[`bench fixtures token-stream snapshots > stable snapshot: tiny 1`] = ` +[ + "Var|khai báo|0-8", + "Identifier|tuổi|9-13", + "=|=|14-15", + "Number|25|16-18", + "Var|khai báo|19-27", + "Identifier|tên|28-31", + "=|=|32-33", + "String|"Tú Nguyễn"|34-45", + "Function|hàm|47-50", + "Identifier|chào|51-55", + "(|(|55-56", + "Identifier|người|56-61", + ")|)|61-62", + "{|{|63-64", + "Return|trả về|67-73", + "String|"Xin chào "|74-85", + "+|+|86-87", + "Identifier|người|88-93", + "}|}|94-95", + "If|nếu|97-100", + "(|(|101-102", + "Identifier|tuổi|102-106", + ">|>|107-108", + "Number|18|109-111", + ")|)|111-112", + "{|{|113-114", + "Identifier|chào|117-121", + "(|(|121-122", + "Identifier|tên|122-125", + ")|)|125-126", + "}|}|127-128", +] +`; diff --git a/packages/parser/src/__test__/bench-fixture-parity.test.ts b/packages/parser/src/__test__/bench-fixture-parity.test.ts new file mode 100644 index 0000000..9ce6824 --- /dev/null +++ b/packages/parser/src/__test__/bench-fixture-parity.test.ts @@ -0,0 +1,28 @@ +import type { Token } from '@vietscript/shared' +import { Parser } from '@parser/parser' +import { Tokenizer } from '@parser/tokenizer' + +import { fixtures } from '../__bench__/fixtures' + +function tokenizeAll(source: string): Token[] { + const parser = new Parser() + parser.syntax = source + parser.tokenizer = new Tokenizer(parser) + const tokens: Token[] = [] + let tok = parser.tokenizer.getNextToken() + while (tok !== null) { + tokens.push(tok) + tok = parser.tokenizer.getNextToken() + } + return tokens +} + +describe('bench fixtures must tokenize without errors', () => { + for (const [name, source] of Object.entries(fixtures)) { + it(`fixture: ${name}`, () => { + expect(() => tokenizeAll(source)).not.toThrow() + const tokens = tokenizeAll(source) + expect(tokens.length).toBeGreaterThan(0) + }, 60_000) + } +}) diff --git a/packages/parser/src/__test__/bench-fixture-snapshot.test.ts b/packages/parser/src/__test__/bench-fixture-snapshot.test.ts new file mode 100644 index 0000000..cd5ad71 --- /dev/null +++ b/packages/parser/src/__test__/bench-fixture-snapshot.test.ts @@ -0,0 +1,28 @@ +import type { Token } from '@vietscript/shared' +import { Parser } from '@parser/parser' +import { Tokenizer } from '@parser/tokenizer' + +import { fixtures } from '../__bench__/fixtures' + +function tokenizeAll(source: string): Token[] { + const parser = new Parser() + parser.syntax = source + parser.tokenizer = new Tokenizer(parser) + const tokens: Token[] = [] + let tok = parser.tokenizer.getNextToken() + while (tok !== null) { + tokens.push(tok) + tok = parser.tokenizer.getNextToken() + } + return tokens +} + +describe('bench fixtures token-stream snapshots', () => { + for (const [name, source] of Object.entries(fixtures)) { + it(`stable snapshot: ${name}`, () => { + const tokens = tokenizeAll(source) + const summary = tokens.map(t => `${t.type}|${t.value}|${t.start}-${t.end}`) + expect(summary).toMatchSnapshot() + }, 60_000) + } +}) diff --git a/packages/parser/src/__test__/tokenizer-fsm.test.ts b/packages/parser/src/__test__/tokenizer-fsm.test.ts new file mode 100644 index 0000000..57b373f --- /dev/null +++ b/packages/parser/src/__test__/tokenizer-fsm.test.ts @@ -0,0 +1,232 @@ +import type { Token } from '@vietscript/shared' +import { Parser } from '@parser/parser' + +import { Tokenizer } from '@parser/tokenizer' +import { TokenizerFSM } from '@parser/tokenizer-fsm' +import { Keyword } from '@vietscript/shared' + +import { fixtures } from '../__bench__/fixtures' + +function tokenizeRegex(source: string): Token[] { + const parser = new Parser() + parser.syntax = source + parser.tokenizer = new Tokenizer(parser) + const out: Token[] = [] + let t = parser.tokenizer.getNextToken() + while (t !== null) { + out.push(t) + t = parser.tokenizer.getNextToken() + } + return out +} + +function tokenizeFSM(source: string): Token[] { + const parser = new Parser() + parser.syntax = source + parser.tokenizer = new TokenizerFSM(parser) + const out: Token[] = [] + let t = parser.tokenizer.getNextToken() + while (t !== null) { + out.push(t) + t = parser.tokenizer.getNextToken() + } + return out +} + +describe('tokenizer-fsm: parity with regex tokenizer', () => { + it('empty input', () => { + expect(tokenizeFSM('')).toEqual(tokenizeRegex('')) + }) + + it('whitespace only', () => { + expect(tokenizeFSM(' \n\t')).toEqual(tokenizeRegex(' \n\t')) + }) + + it('line comment', () => { + expect(tokenizeFSM('// hello\n42')).toEqual(tokenizeRegex('// hello\n42')) + }) + + it('block comment', () => { + expect(tokenizeFSM('/* a\nb */ x')).toEqual(tokenizeRegex('/* a\nb */ x')) + }) + + it('integer literal', () => { + expect(tokenizeFSM('42')).toEqual(tokenizeRegex('42')) + }) + + it('hex literal', () => { + expect(tokenizeFSM('0xFF')).toEqual(tokenizeRegex('0xFF')) + }) + + it('octal literal', () => { + expect(tokenizeFSM('0o17')).toEqual(tokenizeRegex('0o17')) + }) + + it('binary literal', () => { + expect(tokenizeFSM('0b1010')).toEqual(tokenizeRegex('0b1010')) + }) + + it('decimal with fraction', () => { + expect(tokenizeFSM('3.14')).toEqual(tokenizeRegex('3.14')) + }) + + it('exponent literal', () => { + expect(tokenizeFSM('1.5e10')).toEqual(tokenizeRegex('1.5e10')) + }) + + it('bigint literal', () => { + expect(tokenizeFSM('100n')).toEqual(tokenizeRegex('100n')) + }) + + it('numeric separators', () => { + expect(tokenizeFSM('1_000_000')).toEqual(tokenizeRegex('1_000_000')) + }) + + it('leading dot decimal', () => { + expect(tokenizeFSM('.5')).toEqual(tokenizeRegex('.5')) + }) + + it('double-quoted string', () => { + expect(tokenizeFSM('"hello"')).toEqual(tokenizeRegex('"hello"')) + }) + + it('single-quoted string', () => { + expect(tokenizeFSM('\'hello\'')).toEqual(tokenizeRegex('\'hello\'')) + }) + + it('string with escape', () => { + expect(tokenizeFSM('"a\\"b"')).toEqual(tokenizeRegex('"a\\"b"')) + }) + + it('string with unicode escape', () => { + expect(tokenizeFSM('"\\u00E1"')).toEqual(tokenizeRegex('"\\u00E1"')) + }) + + it('template literal simple', () => { + expect(tokenizeFSM('`abc`')).toEqual(tokenizeRegex('`abc`')) + }) + + it('template literal with interpolation', () => { + expect(tokenizeFSM('`a${b}c`')).toEqual(tokenizeRegex('`a${b}c`')) + }) + + it('nested template literal', () => { + expect(tokenizeFSM('`a${`b${c}d`}e`')).toEqual(tokenizeRegex('`a${`b${c}d`}e`')) + }) + + it('regex literal', () => { + expect(tokenizeFSM('x = /abc/g')).toEqual(tokenizeRegex('x = /abc/g')) + }) + + it('division vs regex (after identifier)', () => { + expect(tokenizeFSM('x / y')).toEqual(tokenizeRegex('x / y')) + }) + + it('regex with character class', () => { + expect(tokenizeFSM('var r = /[a-z]+/i')).toEqual(tokenizeRegex('var r = /[a-z]+/i')) + }) + + it('all single-char operators', () => { + expect(tokenizeFSM('+-*/%~!&|^?:.,;')).toEqual(tokenizeRegex('+-*/%~!&|^?:.,;')) + }) + + it('compound operator >>>=', () => { + expect(tokenizeFSM('a >>>= b')).toEqual(tokenizeRegex('a >>>= b')) + }) + + it('arrow function tokens', () => { + expect(tokenizeFSM('(a, b) => a + b')).toEqual(tokenizeRegex('(a, b) => a + b')) + }) + + it('all bracket types', () => { + expect(tokenizeFSM('([{}])')).toEqual(tokenizeRegex('([{}])')) + }) + + it('english keyword: var', () => { + expect(tokenizeFSM('var x = 1')).toEqual(tokenizeRegex('var x = 1')) + }) + + it('vietnamese keyword: khai báo', () => { + expect(tokenizeFSM('khai báo x = 1')).toEqual(tokenizeRegex('khai báo x = 1')) + }) + + it('vietnamese keyword: phá vòng lặp', () => { + expect(tokenizeFSM('phá vòng lặp')).toEqual(tokenizeRegex('phá vòng lặp')) + }) + + it('vietnamese keyword: kiểu của', () => { + expect(tokenizeFSM('kiểu của x')).toEqual(tokenizeRegex('kiểu của x')) + }) + + it('multi-word identifier (no embedded keyword)', () => { + expect(tokenizeFSM('khai báo con mèo đẹp = 1')).toEqual(tokenizeRegex('khai báo con mèo đẹp = 1')) + }) + + it('embedded keyword inside identifier should split', () => { + const fsm = tokenizeFSM('khai báo một lớp gì đó = 1') + const rx = tokenizeRegex('khai báo một lớp gì đó = 1') + expect(fsm).toEqual(rx) + }) + + it('boolean keywords vi/en', () => { + expect(tokenizeFSM('đúng sai true false')).toEqual(tokenizeRegex('đúng sai true false')) + }) + + it('null/Infinity/NaN/undefined', () => { + expect(tokenizeFSM('null Infinity NaN undefined rỗng vô cực không xác định')).toEqual(tokenizeRegex('null Infinity NaN undefined rỗng vô cực không xác định')) + }) + + it('throw on unknown char', () => { + expect(() => tokenizeFSM('@@@')).toThrow() + }) + + it('riêng tư followed by identifier should not consume trailing letters', () => { + const t = tokenizeFSM('riêng tư x') + expect(t).toHaveLength(2) + expect(t[0].type).toBe(Keyword.PRIVATE) + expect(t[1].value).toBe('x') + }) + + it('bảo vệ keyword followed by identifier', () => { + const t = tokenizeFSM('bảo vệ phương thức') + expect(t).toHaveLength(2) + expect(t[0].type).toBe(Keyword.PROTECTED) + expect(t[1].type).toBe(Keyword.IDENTIFIER) + }) +}) + +describe('tokenizer-fsm: produces identical output to regex tokenizer on bench fixtures', () => { + for (const [name, source] of Object.entries(fixtures)) { + it(`fixture parity: ${name}`, () => { + const rx = tokenizeRegex(source) + const fsm = tokenizeFSM(source) + expect(fsm.length).toBe(rx.length) + for (let i = 0; i < rx.length; i++) { + expect({ idx: i, ...fsm[i] }).toEqual({ idx: i, ...rx[i] }) + } + }, 60_000) + } +}) + +describe('tokenizer-fsm: rollback works', () => { + it('rollback decrements cursor and lookahead end', () => { + const parser = new Parser() + parser.syntax = 'abc; def' + parser.tokenizer = new TokenizerFSM(parser) + const first = parser.tokenizer.getNextToken() + expect(first?.value).toBe('abc') + const before = (parser.tokenizer as TokenizerFSM).getCursor() + ;(parser.tokenizer as TokenizerFSM).rollback(2) + expect((parser.tokenizer as TokenizerFSM).getCursor()).toBe(before - 2) + }) +}) + +describe('tokenizer-fsm: isEOF', () => { + it('is true at end', () => { + const parser = new Parser() + parser.syntax = 'a' + parser.tokenizer = new TokenizerFSM(parser) + parser.tokenizer.getNextToken() + expect(parser.tokenizer.isEOF()).toBe(true) + }) +}) diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index e855540..aa68b96 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -6,7 +6,9 @@ export default parser export { VietScriptError } from './errors' export { Parser } from './parser' +export type { ITokenizer, ParserOptions, TokenizerKind } from './parser' export { Tokenizer } from './tokenizer' +export { TokenizerFSM } from './tokenizer-fsm' if (typeof window !== 'undefined') { (window as unknown as { VietScript: { parser: Parser } }).VietScript = { parser } diff --git a/packages/parser/src/nodes/literals/TemplateLiteral.ts b/packages/parser/src/nodes/literals/TemplateLiteral.ts index 720e8b5..4df5a45 100644 --- a/packages/parser/src/nodes/literals/TemplateLiteral.ts +++ b/packages/parser/src/nodes/literals/TemplateLiteral.ts @@ -1,5 +1,4 @@ -import { Parser } from '@parser/parser' -import { Tokenizer } from '@parser/tokenizer' +import { createTokenizer, Parser } from '@parser/parser' import { Expression } from '../expressions/Expression' @@ -123,9 +122,9 @@ export class TemplateLiteral { } for (const exprSource of expressions) { - const subParser = new Parser() + const subParser = new Parser({ tokenizer: parser.tokenizerKind }) subParser.syntax = exprSource - subParser.tokenizer = new Tokenizer(subParser) + subParser.tokenizer = createTokenizer(subParser) subParser.lookahead = subParser.tokenizer.getNextToken() this.expressions.push(new Expression(subParser)) } diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 16bfa6c..a695ee1 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -4,11 +4,32 @@ import { Keyword } from '@vietscript/shared' import { VietScriptError } from './errors' import { Program } from './nodes/Program' import { Tokenizer } from './tokenizer' +import { TokenizerFSM } from './tokenizer-fsm' + +export type TokenizerKind = 'regex' | 'fsm' + +export interface ITokenizer { + getNextToken: () => Token | null + isEOF: () => boolean + rollback: (step: number) => number +} + +export interface ParserOptions { + tokenizer?: TokenizerKind +} + +export function createTokenizer(parser: Parser): ITokenizer { + return parser.tokenizerKind === 'fsm' + ? new TokenizerFSM(parser) + : new Tokenizer(parser) +} export class Parser { public syntax: string - public tokenizer: Tokenizer + public tokenizer: ITokenizer + + public tokenizerKind: TokenizerKind public lookahead: Token | null @@ -16,15 +37,16 @@ export class Parser { public ternaryDepth = 0 - constructor() { + constructor(options: ParserOptions = {}) { this.syntax = '' - this.tokenizer = new Tokenizer(this) + this.tokenizerKind = options.tokenizer ?? 'fsm' + this.tokenizer = createTokenizer(this) this.lookahead = null } public parse(syntax: string, InitAtsNodeClass?: new (parser: Parser) => unknown): any { this.syntax = syntax - this.tokenizer = new Tokenizer(this) + this.tokenizer = createTokenizer(this) this.lookahead = this.tokenizer.getNextToken() if (InitAtsNodeClass) diff --git a/packages/parser/src/tokenizer-fsm.ts b/packages/parser/src/tokenizer-fsm.ts new file mode 100644 index 0000000..b67709b --- /dev/null +++ b/packages/parser/src/tokenizer-fsm.ts @@ -0,0 +1,917 @@ +import type { Token } from '@vietscript/shared' +import type { Parser } from './parser' + +import { Keyword } from '@vietscript/shared' + +// Boundary kinds (post-keyword check): +// WORD: next char must not be in [A-Za-z0-9_] (mimics JS \b after ASCII word). +// IDENT: next char must not be in [A-Za-zÀ-ỹ] (mimics negative +// lookahead used for VI keywords ending in non-ASCII). +// NONE: no boundary check (mimics keywords without \b such as `else`, +// `return`, `try`, `as`, `from`, `const`, `async`). +const Boundary = { + WORD: 0, + IDENT: 1, + NONE: 2, +} as const + +type BoundaryKind = typeof Boundary[keyof typeof Boundary] + +interface KeywordEntry { + text: string + type: Keyword + boundary: BoundaryKind +} + +// Mirrors the regex spec table in constants/specs.ts. The FSM walks a trie +// built from these entries to detect keywords; ordering does not matter here +// because the trie picks the longest valid match with a passing boundary. +const KEYWORDS: ReadonlyArray = [ + { text: 'var', type: Keyword.VAR, boundary: Boundary.WORD }, + { text: 'khai báo', type: Keyword.VAR, boundary: Boundary.WORD }, + { text: 'break', type: Keyword.BREAK, boundary: Boundary.WORD }, + { text: 'phá vòng lặp', type: Keyword.BREAK, boundary: Boundary.WORD }, + { text: 'do', type: Keyword.DO, boundary: Boundary.WORD }, + { text: 'thực hiện', type: Keyword.DO, boundary: Boundary.WORD }, + { text: 'instanceof', type: Keyword.INSTANCEOF, boundary: Boundary.WORD }, + { text: 'là kiểu', type: Keyword.INSTANCEOF, boundary: Boundary.WORD }, + { text: 'typeof', type: Keyword.TYPEOF, boundary: Boundary.WORD }, + { text: 'kiểu của', type: Keyword.TYPEOF, boundary: Boundary.WORD }, + { text: 'switch', type: Keyword.SWITCH, boundary: Boundary.WORD }, + { text: 'duyệt', type: Keyword.SWITCH, boundary: Boundary.WORD }, + { text: 'case', type: Keyword.CASE, boundary: Boundary.WORD }, + { text: 'trường hợp', type: Keyword.CASE, boundary: Boundary.WORD }, + { text: 'if', type: Keyword.IF, boundary: Boundary.WORD }, + { text: 'nếu', type: Keyword.IF, boundary: Boundary.WORD }, + { text: 'else', type: Keyword.ELSE, boundary: Boundary.NONE }, + { text: 'không thì', type: Keyword.ELSE, boundary: Boundary.NONE }, + { text: 'new', type: Keyword.NEW, boundary: Boundary.WORD }, + { text: 'catch', type: Keyword.CATCH, boundary: Boundary.WORD }, + { text: 'bắt lỗi', type: Keyword.CATCH, boundary: Boundary.WORD }, + { text: 'finally', type: Keyword.FINALLY, boundary: Boundary.WORD }, + { text: 'cuối cùng', type: Keyword.FINALLY, boundary: Boundary.WORD }, + { text: 'return', type: Keyword.RETURN, boundary: Boundary.NONE }, + { text: 'trả về', type: Keyword.RETURN, boundary: Boundary.NONE }, + { text: 'void', type: Keyword.VOID, boundary: Boundary.WORD }, + { text: 'continue', type: Keyword.CONTINUE, boundary: Boundary.WORD }, + { text: 'tiếp tục', type: Keyword.CONTINUE, boundary: Boundary.WORD }, + { text: 'for', type: Keyword.FOR, boundary: Boundary.WORD }, + { text: 'lặp', type: Keyword.FOR, boundary: Boundary.WORD }, + { text: 'while', type: Keyword.WHILE, boundary: Boundary.WORD }, + { text: 'khi mà', type: Keyword.WHILE, boundary: Boundary.IDENT }, + { text: 'debugger', type: Keyword.DEBUGGER, boundary: Boundary.WORD }, + { text: 'function', type: Keyword.FUNCTION, boundary: Boundary.WORD }, + { text: 'hàm', type: Keyword.FUNCTION, boundary: Boundary.WORD }, + { text: 'this', type: Keyword.THIS, boundary: Boundary.WORD }, + { text: 'đây', type: Keyword.THIS, boundary: Boundary.WORD }, + { text: 'with', type: Keyword.WITH, boundary: Boundary.WORD }, + { text: 'default', type: Keyword.DEFAULT, boundary: Boundary.WORD }, + { text: 'mặc định', type: Keyword.DEFAULT, boundary: Boundary.WORD }, + { text: 'throw', type: Keyword.THROW, boundary: Boundary.WORD }, + { text: 'báo lỗi', type: Keyword.THROW, boundary: Boundary.WORD }, + { text: 'delete', type: Keyword.DELETE, boundary: Boundary.WORD }, + { text: 'xoá', type: Keyword.DELETE, boundary: Boundary.IDENT }, + { text: 'in', type: Keyword.IN, boundary: Boundary.WORD }, + { text: 'trong', type: Keyword.IN, boundary: Boundary.WORD }, + { text: 'of', type: Keyword.OF, boundary: Boundary.WORD }, + { text: 'của', type: Keyword.OF, boundary: Boundary.WORD }, + { text: 'try', type: Keyword.TRY, boundary: Boundary.NONE }, + { text: 'thử', type: Keyword.TRY, boundary: Boundary.NONE }, + { text: 'as', type: Keyword.AS, boundary: Boundary.NONE }, + { text: 'như là', type: Keyword.AS, boundary: Boundary.NONE }, + { text: 'from', type: Keyword.FROM, boundary: Boundary.NONE }, + { text: 'từ', type: Keyword.FROM, boundary: Boundary.NONE }, + { text: 'const', type: Keyword.CONST, boundary: Boundary.NONE }, + { text: 'hằng số', type: Keyword.CONST, boundary: Boundary.NONE }, + { text: 'class', type: Keyword.CLASS, boundary: Boundary.WORD }, + { text: 'lớp', type: Keyword.CLASS, boundary: Boundary.WORD }, + { text: 'super', type: Keyword.SUPER, boundary: Boundary.WORD }, + { text: 'khởi tạo cha', type: Keyword.SUPER, boundary: Boundary.WORD }, + { text: 'constructor', type: Keyword.CONSTRUCTOR, boundary: Boundary.WORD }, + { text: 'khởi tạo', type: Keyword.CONSTRUCTOR, boundary: Boundary.WORD }, + { text: 'extends', type: Keyword.EXTENDS, boundary: Boundary.WORD }, + { text: 'kế thừa', type: Keyword.EXTENDS, boundary: Boundary.WORD }, + { text: 'export', type: Keyword.EXPORT, boundary: Boundary.WORD }, + { text: 'cho phép', type: Keyword.EXPORT, boundary: Boundary.WORD }, + { text: 'import', type: Keyword.IMPORT, boundary: Boundary.WORD }, + { text: 'sử dụng', type: Keyword.IMPORT, boundary: Boundary.WORD }, + { text: 'async', type: Keyword.ASYNC, boundary: Boundary.NONE }, + { text: 'bất đồng bộ', type: Keyword.ASYNC, boundary: Boundary.NONE }, + { text: 'await', type: Keyword.AWAIT, boundary: Boundary.WORD }, + { text: 'chờ', type: Keyword.AWAIT, boundary: Boundary.IDENT }, + { text: 'yield', type: Keyword.YIELD, boundary: Boundary.WORD }, + { text: 'nhường', type: Keyword.YIELD, boundary: Boundary.WORD }, + { text: 'let', type: Keyword.LET, boundary: Boundary.WORD }, + { text: 'biến', type: Keyword.LET, boundary: Boundary.WORD }, + { text: 'private', type: Keyword.PRIVATE, boundary: Boundary.WORD }, + { text: 'riêng tư', type: Keyword.PRIVATE, boundary: Boundary.IDENT }, + { text: 'public', type: Keyword.PUBLIC, boundary: Boundary.WORD }, + { text: 'công khai', type: Keyword.PUBLIC, boundary: Boundary.WORD }, + { text: 'protected', type: Keyword.PROTECTED, boundary: Boundary.WORD }, + { text: 'bảo vệ', type: Keyword.PROTECTED, boundary: Boundary.IDENT }, + { text: 'static', type: Keyword.STATIC, boundary: Boundary.WORD }, + { text: 'tĩnh', type: Keyword.STATIC, boundary: Boundary.WORD }, + { text: 'get', type: Keyword.GET, boundary: Boundary.WORD }, + { text: 'lấy', type: Keyword.GET, boundary: Boundary.WORD }, + { text: 'set', type: Keyword.SET, boundary: Boundary.WORD }, + { text: 'gán', type: Keyword.SET, boundary: Boundary.WORD }, + { text: 'null', type: Keyword.NULL, boundary: Boundary.WORD }, + { text: 'rỗng', type: Keyword.NULL, boundary: Boundary.WORD }, + { text: 'NaN', type: Keyword.NAN, boundary: Boundary.WORD }, + { text: 'Infinity', type: Keyword.INFINITY, boundary: Boundary.WORD }, + { text: 'vô cực', type: Keyword.INFINITY, boundary: Boundary.WORD }, + { text: 'undefined', type: Keyword.UNDEFINED, boundary: Boundary.WORD }, + { text: 'không xác định', type: Keyword.UNDEFINED, boundary: Boundary.WORD }, + { text: 'true', type: Keyword.BOOLEAN, boundary: Boundary.WORD }, + { text: 'false', type: Keyword.BOOLEAN, boundary: Boundary.WORD }, + { text: 'đúng', type: Keyword.BOOLEAN, boundary: Boundary.WORD }, + { text: 'sai', type: Keyword.BOOLEAN, boundary: Boundary.WORD }, +] + +class TrieNode { + children = new Map() + type: Keyword | null = null + boundary: BoundaryKind = Boundary.WORD +} + +const KEYWORD_TRIE: TrieNode = (() => { + const root = new TrieNode() + for (const entry of KEYWORDS) { + let node = root + for (let i = 0; i < entry.text.length; i++) { + const code = entry.text.charCodeAt(i) + let child = node.children.get(code) + if (!child) { + child = new TrieNode() + node.children.set(code, child) + } + node = child + } + if (node.type === null) { + node.type = entry.type + node.boundary = entry.boundary + } + } + return root +})() + +// Operator longest-match tree. Built from a flat list of operator strings. +// Order independent — at lookup time we walk character-by-character and keep +// track of the longest valid operator end seen. +const OPERATORS: readonly string[] = [ + '...', + '.', + '>>>=', + '>>>', + '>>=', + '>>', + '<<=', + '<<', + '<=', + '>=', + '<', + '>', + '===', + '!==', + '==', + '!=', + '=>', + '**=', + '**', + '*=', + '*', + '++', + '+=', + '+', + '--', + '-=', + '-', + '/=', + '/', + '%=', + '%', + '&&=', + '&&', + '&=', + '&', + '||=', + '||', + '|=', + '|', + '^=', + '^', + '~', + '!', + '??=', + '??', + '?.', + '?', + '=', + '[', + ']', + '(', + ')', + '{', + '}', + ';', + ',', + ':', + '#', +] + +class OperatorNode { + children = new Map() + value: string | null = null +} + +const OPERATOR_TRIE: OperatorNode = (() => { + const root = new OperatorNode() + for (const op of OPERATORS) { + let node = root + for (let i = 0; i < op.length; i++) { + const code = op.charCodeAt(i) + let child = node.children.get(code) + if (!child) { + child = new OperatorNode() + node.children.set(code, child) + } + node = child + } + node.value = op + } + return root +})() + +const REGEX_PRECEDING_TOKENS = new Set([ + '(', + '[', + '{', + ',', + ';', + ':', + '=', + '!', + '?', + '+', + '-', + '*', + '/', + '%', + '&&', + '||', + '??', + '=>', + '==', + '===', + '!=', + '!==', + '<', + '>', + '<=', + '>=', + '&', + '|', + '^', + '~', + '<<', + '>>', + '>>>', + '+=', + '-=', + '*=', + '/=', + '%=', + '**=', + '&=', + '|=', + '^=', + '<<=', + '>>=', + '>>>=', + '&&=', + '||=', + '??=', + '...', + Keyword.RETURN, + Keyword.YIELD, + Keyword.AWAIT, + Keyword.TYPEOF, + Keyword.VOID, + Keyword.DELETE, + Keyword.NEW, + Keyword.THROW, + Keyword.IN, + Keyword.OF, + Keyword.INSTANCEOF, + Keyword.CASE, + Keyword.DEFAULT, +]) + +function isWhitespace(code: number): boolean { + // Mirrors JS \s minus what we don't expect in source. We rely on String.prototype + // to be lenient — anything not handled below falls through to the operator + // dispatcher and throws cleanly. + return code === 0x20 /* space */ + || code === 0x09 /* tab */ + || code === 0x0A /* LF */ + || code === 0x0D /* CR */ + || code === 0x0B /* VT */ + || code === 0x0C /* FF */ + || code === 0xA0 /* NBSP */ +} + +function isAsciiLetter(code: number): boolean { + return (code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A) +} + +function isVietnameseLetter(code: number): boolean { + return code >= 0x00C0 && code <= 0x1EF9 +} + +function isIdentStart(code: number): boolean { + return isAsciiLetter(code) || isVietnameseLetter(code) +} + +function isDigit(code: number): boolean { + return code >= 0x30 && code <= 0x39 +} + +function isIdentCont(code: number): boolean { + return isIdentStart(code) || isDigit(code) +} + +function isHexDigit(code: number): boolean { + return isDigit(code) + || (code >= 0x41 && code <= 0x46) + || (code >= 0x61 && code <= 0x66) +} + +function isOctalDigit(code: number): boolean { + return code >= 0x30 && code <= 0x37 +} + +function isBinaryDigit(code: number): boolean { + return code === 0x30 || code === 0x31 +} + +function isAsciiWordChar(code: number): boolean { + return isAsciiLetter(code) || isDigit(code) || code === 0x5F /* _ */ +} + +export class TokenizerFSM { + private parser: Parser + + private cursor: number + + private lastTokenType: string | null = null + + constructor(parser: Parser) { + this.parser = parser + this.cursor = 0 + } + + public getCursor(): number { + return this.cursor + } + + public rollback(step: number): number { + if (this.parser.lookahead) + this.parser.lookahead.end -= step + this.cursor -= step + return this.cursor + } + + public isEOF(): boolean { + return this.cursor === this.parser.syntax.length + } + + protected hasMoreTokens(): boolean { + return this.cursor < this.parser.syntax.length + } + + public getNextToken(): Token | null { + const source = this.parser.syntax + const length = source.length + + while (this.cursor < length) { + const start = this.cursor + const code = source.charCodeAt(this.cursor) + + if (isWhitespace(code)) { + this.cursor++ + continue + } + + // Line comment: //... + if (code === 0x2F && source.charCodeAt(this.cursor + 1) === 0x2F) { + this.cursor += 2 + while (this.cursor < length && source.charCodeAt(this.cursor) !== 0x0A) { + this.cursor++ + } + continue + } + + // Block comment: /* ... */ + if (code === 0x2F && source.charCodeAt(this.cursor + 1) === 0x2A) { + this.cursor += 2 + while (this.cursor < length) { + if (source.charCodeAt(this.cursor) === 0x2A + && source.charCodeAt(this.cursor + 1) === 0x2F) { + this.cursor += 2 + break + } + this.cursor++ + } + continue + } + + // Template literal + if (code === 0x60) { + return this.scanTemplateLiteral(start) + } + + // Regex literal (context-sensitive) + if (code === 0x2F && this.isRegexExpected()) { + const tok = this.scanRegexLiteral(start) + if (tok !== null) { + this.lastTokenType = tok.type as string + return tok + } + } + + // String literals + if (code === 0x22 || code === 0x27) { + return this.scanString(start, code) + } + + // Numeric literals: digits, or `.` followed by digit + if (isDigit(code)) { + return this.scanNumber(start) + } + if (code === 0x2E /* . */ && isDigit(source.charCodeAt(this.cursor + 1))) { + return this.scanNumber(start) + } + + // Identifier / keyword + if (isIdentStart(code)) { + return this.scanIdentifierOrKeyword(start) + } + + // Operator (longest match via trie) + const opTok = this.scanOperator(start) + if (opTok !== null) { + return opTok + } + + throw new SyntaxError(`Unexpected token: "${source[this.cursor]}"`) + } + + return null + } + + private isRegexExpected(): boolean { + if (this.lastTokenType === null) + return true + return REGEX_PRECEDING_TOKENS.has(this.lastTokenType) + } + + private scanString(start: number, quote: number): Token { + const source = this.parser.syntax + const length = source.length + let i = start + 1 + while (i < length) { + const ch = source.charCodeAt(i) + if (ch === 0x5C /* \ */) { + i += 2 + continue + } + if (ch === quote) { + i++ + const value = source.slice(start, i) + this.cursor = i + this.lastTokenType = Keyword.STRING + return { + type: Keyword.STRING, + value, + start, + end: i, + } + } + i++ + } + throw new SyntaxError(`Unterminated string literal at ${start}`) + } + + private scanNumber(start: number): Token { + const source = this.parser.syntax + const length = source.length + let i = start + + // Leading dot decimal: .5, .5e2, .5n (n probably nonsense, but match regex) + if (source.charCodeAt(i) === 0x2E /* . */) { + i++ + while (i < length) { + const c = source.charCodeAt(i) + if (isDigit(c) || c === 0x5F) { + i++ + continue + } + break + } + i = this.consumeExponent(i) + i = this.consumeBigIntSuffix(i) + return this.emitNumber(start, i) + } + + // 0x / 0o / 0b + if (source.charCodeAt(i) === 0x30) { + const next = source.charCodeAt(i + 1) + if (next === 0x78 || next === 0x58) { + i += 2 + while (i < length) { + const c = source.charCodeAt(i) + if (isHexDigit(c) || c === 0x5F) { + i++ + continue + } + break + } + i = this.consumeBigIntSuffix(i) + return this.emitNumber(start, i) + } + if (next === 0x6F || next === 0x4F) { + i += 2 + while (i < length) { + const c = source.charCodeAt(i) + if (isOctalDigit(c) || c === 0x5F) { + i++ + continue + } + break + } + i = this.consumeBigIntSuffix(i) + return this.emitNumber(start, i) + } + if (next === 0x62 || next === 0x42) { + i += 2 + while (i < length) { + const c = source.charCodeAt(i) + if (isBinaryDigit(c) || c === 0x5F) { + i++ + continue + } + break + } + i = this.consumeBigIntSuffix(i) + return this.emitNumber(start, i) + } + } + + // Decimal: digits[_digits]*[.digits[_digits]*]? + while (i < length) { + const c = source.charCodeAt(i) + if (isDigit(c) || c === 0x5F) { + i++ + continue + } + break + } + if (source.charCodeAt(i) === 0x2E /* . */) { + i++ + while (i < length) { + const c = source.charCodeAt(i) + if (isDigit(c) || c === 0x5F) { + i++ + continue + } + break + } + } + i = this.consumeExponent(i) + i = this.consumeBigIntSuffix(i) + return this.emitNumber(start, i) + } + + private consumeExponent(i: number): number { + const source = this.parser.syntax + const length = source.length + const c = source.charCodeAt(i) + if (c !== 0x65 && c !== 0x45 /* e/E */) { + return i + } + let j = i + 1 + const sign = source.charCodeAt(j) + if (sign === 0x2B || sign === 0x2D /* + or - */) { + j++ + } + if (!isDigit(source.charCodeAt(j))) { + return i + } + j++ + while (j < length) { + const cc = source.charCodeAt(j) + if (isDigit(cc) || cc === 0x5F) { + j++ + continue + } + break + } + return j + } + + private consumeBigIntSuffix(i: number): number { + if (this.parser.syntax.charCodeAt(i) === 0x6E /* n */) { + return i + 1 + } + return i + } + + private emitNumber(start: number, end: number): Token { + const value = this.parser.syntax.slice(start, end) + this.cursor = end + this.lastTokenType = Keyword.NUMBER + return { + type: Keyword.NUMBER, + value, + start, + end, + } + } + + private scanIdentifierOrKeyword(start: number): Token { + // Try keyword trie first; on a hit with passing boundary we emit the keyword. + // Otherwise we fall back to identifier scanning (with multi-word support + // and embedded-keyword truncation). + const kw = this.matchKeyword(start) + if (kw !== null) { + this.cursor = kw.end + this.lastTokenType = kw.type as string + return { + type: kw.type, + value: this.parser.syntax.slice(start, kw.end), + start, + end: kw.end, + } + } + return this.scanIdentifier(start) + } + + private matchKeyword(start: number): { type: Keyword, end: number } | null { + const source = this.parser.syntax + const length = source.length + let node: TrieNode | undefined = KEYWORD_TRIE + let i = start + let bestType: Keyword | null = null + let bestEnd = -1 + + while (i < length && node !== undefined) { + const code = source.charCodeAt(i) + const next = node.children.get(code) + if (next === undefined) + break + i++ + node = next + if (node.type !== null && this.boundaryOk(node.boundary, i)) { + bestType = node.type + bestEnd = i + } + } + + if (bestType !== null && bestEnd !== -1) { + return { type: bestType, end: bestEnd } + } + return null + } + + private boundaryOk(kind: BoundaryKind, end: number): boolean { + if (kind === Boundary.NONE) + return true + if (end >= this.parser.syntax.length) + return true + const code = this.parser.syntax.charCodeAt(end) + if (kind === Boundary.WORD) { + // Mimic JS \b after ASCII word char: next must not be [A-Za-z0-9_] + return !isAsciiWordChar(code) + } + // IDENT: next must not be [A-Za-zÀ-ỹ] + return !isIdentStart(code) + } + + private scanIdentifier(start: number): Token { + const source = this.parser.syntax + const length = source.length + let i = start + if (!isIdentStart(source.charCodeAt(i))) { + throw new SyntaxError(`Unexpected token: "${source[i]}"`) + } + i++ + while (i < length && isIdentCont(source.charCodeAt(i))) { + i++ + } + + // Multi-word identifier: consume ` ` repeatedly, but stop before + // a word that would itself start a keyword at this position. + while (i < length) { + if (source.charCodeAt(i) !== 0x20 /* space */) + break + const wordStart = i + 1 + if (wordStart >= length) + break + const wordCode = source.charCodeAt(wordStart) + if (!isIdentStart(wordCode)) + break + // Embedded-keyword check: starting from wordStart, would a keyword + // match? If yes, do NOT consume the space, terminate identifier here. + if (this.matchKeyword(wordStart) !== null) { + break + } + i = wordStart + 1 + while (i < length && isIdentCont(source.charCodeAt(i))) { + i++ + } + } + + const value = source.slice(start, i) + this.cursor = i + this.lastTokenType = Keyword.IDENTIFIER + return { + type: Keyword.IDENTIFIER, + value, + start, + end: i, + } + } + + private scanOperator(start: number): Token | null { + const source = this.parser.syntax + const length = source.length + let node: OperatorNode | undefined = OPERATOR_TRIE + let i = start + let bestEnd = -1 + let bestValue: string | null = null + + while (i < length && node !== undefined) { + const code = source.charCodeAt(i) + const next = node.children.get(code) + if (next === undefined) + break + i++ + node = next + if (node.value !== null) { + bestEnd = i + bestValue = node.value + } + } + + if (bestValue === null || bestEnd === -1) { + return null + } + this.cursor = bestEnd + this.lastTokenType = bestValue + return { + type: bestValue, + value: bestValue, + start, + end: bestEnd, + } + } + + private scanRegexLiteral(start: number): Token | null { + const source = this.parser.syntax + const length = source.length + let i = start + 1 + let inCharClass = false + + while (i < length) { + const ch = source.charCodeAt(i) + if (ch === 0x5C /* \ */) { + i += 2 + continue + } + if (ch === 0x5B /* [ */) { + inCharClass = true + i++ + continue + } + if (ch === 0x5D /* ] */) { + inCharClass = false + i++ + continue + } + if (ch === 0x2F /* / */ && !inCharClass) { + i++ + while (i < length) { + const fc = source.charCodeAt(i) + if (fc >= 0x61 && fc <= 0x7A) { + i++ + continue + } + break + } + const value = source.slice(start, i) + this.cursor = i + return { + type: 'RegExpLiteral', + value, + start, + end: i, + } + } + if (ch === 0x0A /* \n */) { + return null + } + i++ + } + return null + } + + private scanTemplateLiteral(start: number): Token { + const source = this.parser.syntax + const length = source.length + let i = start + 1 + + while (i < length) { + const ch = source.charCodeAt(i) + + if (ch === 0x5C /* \ */) { + i += 2 + continue + } + + if (ch === 0x60 /* ` */) { + i++ + const value = source.slice(start, i) + this.cursor = i + this.lastTokenType = 'TemplateLiteral' + return { + type: 'TemplateLiteral', + value, + start, + end: i, + } + } + + if (ch === 0x24 /* $ */ && source.charCodeAt(i + 1) === 0x7B /* { */) { + i += 2 + let depth = 1 + while (i < length && depth > 0) { + const inner = source.charCodeAt(i) + if (inner === 0x5C) { + i += 2 + continue + } + if (inner === 0x22 || inner === 0x27) { + const quote = inner + i++ + while (i < length && source.charCodeAt(i) !== quote) { + if (source.charCodeAt(i) === 0x5C) + i++ + i++ + } + i++ + continue + } + if (inner === 0x60 /* ` */) { + i++ + while (i < length) { + if (source.charCodeAt(i) === 0x5C) { + i += 2 + continue + } + if (source.charCodeAt(i) === 0x24 + && source.charCodeAt(i + 1) === 0x7B) { + i += 2 + let innerDepth = 1 + while (i < length && innerDepth > 0) { + const ic = source.charCodeAt(i) + if (ic === 0x7B) + innerDepth++ + else if (ic === 0x7D) + innerDepth-- + i++ + } + continue + } + if (source.charCodeAt(i) === 0x60) { + i++ + break + } + i++ + } + continue + } + if (inner === 0x7B) + depth++ + else if (inner === 0x7D) + depth-- + i++ + } + continue + } + + i++ + } + + throw new SyntaxError(`Template literal không đóng, bắt đầu tại vị trí ${start}`) + } +} diff --git a/vitest.config.ts b/vitest.config.ts index aeb8587..148da0d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -23,15 +23,26 @@ export default defineConfig({ '**/.nx/**', '**/.vitepress/cache/**', '**/coverage/**', + '**/__bench__/**', + '**/*.bench.?(c|m)[jt]s?(x)', ], + benchmark: { + include: ['packages/**/src/**/*.bench.?(c|m)[jt]s?(x)'], + exclude: [ + '**/node_modules/**', + '**/dist/**', + ], + }, coverage: { provider: 'v8', reporter: ['text', 'html', 'json-summary'], include: ['packages/parser/src/**', 'packages/shared/**'], exclude: [ '**/__test__/**', + '**/__bench__/**', '**/*.test.ts', '**/*.spec.ts', + '**/*.bench.ts', '**/dist/**', '**/node_modules/**', ],