├── .vscode └── settings.json ├── .gitpod.yml ├── generateTest.ts ├── deno.json ├── LICENSE ├── .gitignore ├── deno.lock ├── mod.ts └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true 3 | } 4 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # List the start up tasks. Learn more: https://www.gitpod.io/docs/configure/workspaces/tasks 2 | tasks: 3 | - name: Install Deno 4 | command: | 5 | curl -fsSL https://deno.land/install.sh | sh 6 | export DENO_INSTALL="/home/gitpod/.deno" 7 | export PATH="$DENO_INSTALL/bin:$PATH" -------------------------------------------------------------------------------- /generateTest.ts: -------------------------------------------------------------------------------- 1 | const filePath = "./README.md"; 2 | 3 | const text = await Deno.readTextFile(filePath); 4 | 5 | const lines = text.split("\n"); 6 | 7 | let code = ""; 8 | let codeBlock = ""; 9 | let inCodeBlock = false; 10 | 11 | for (const line of lines) { 12 | if (line.startsWith("---")) { 13 | inCodeBlock = true; 14 | } else if (inCodeBlock && codeBlock === "" && line.startsWith("```")) { 15 | codeBlock += "\n"; 16 | } else if (inCodeBlock && codeBlock !== "" && line.startsWith("```")) { 17 | code += codeBlock; 18 | codeBlock = ""; 19 | } else if (inCodeBlock && codeBlock !== "") { 20 | codeBlock += line + "\n"; 21 | } 22 | } 23 | 24 | Deno.writeTextFile("README.test.ts", code); 25 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@core/match", 3 | "version": "0.4.1", 4 | "exports": "./mod.ts", 5 | "tasks": { 6 | "generateTest": "deno run --allow-write --allow-read generateTest.ts", 7 | "test": "deno task generateTest && deno test", 8 | "update": "deno run --allow-env --allow-read --allow-write --allow-net --allow-run=git,deno https://deno.land/x/molt/cli.ts ./**/*.ts --unstable-lock", 9 | "update:commit": "deno task -q update --commit --prefix 'build(deps):' --prefix-lock 'build(lock):'" 10 | }, 11 | "publish": { 12 | "exclude": [ 13 | ".vscode/**", 14 | ".git", 15 | ".gitignore", 16 | "README.test.ts", 17 | ".gitpod.yml", 18 | "deno.lock", 19 | "generateTest.ts" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 TANIGUCHI Masaya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # deno 133 | *.test.ts -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "packages": { 4 | "specifiers": { 5 | "jsr:@core/unknownutil@3.18.0": "jsr:@core/unknownutil@3.18.0", 6 | "jsr:@std/assert": "jsr:@std/assert@0.218.2", 7 | "jsr:@std/fmt@^0.218.2": "jsr:@std/fmt@0.218.2", 8 | "npm:ts-toolbelt@9.6.0": "npm:ts-toolbelt@9.6.0" 9 | }, 10 | "jsr": { 11 | "@core/unknownutil@3.18.0": { 12 | "integrity": "bff7ab4a2f554bbade301127519523b0d3baa4273ecbed51287133ac00a48738" 13 | }, 14 | "@std/assert@0.218.2": { 15 | "integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf", 16 | "dependencies": [ 17 | "jsr:@std/fmt@^0.218.2" 18 | ] 19 | }, 20 | "@std/fmt@0.218.2": { 21 | "integrity": "99526449d2505aa758b6cbef81e7dd471d8b28ec0dcb1491d122b284c548788a" 22 | } 23 | }, 24 | "npm": { 25 | "ts-toolbelt@9.6.0": { 26 | "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", 27 | "dependencies": {} 28 | } 29 | } 30 | }, 31 | "redirects": { 32 | "https://deno.land/x/dnt/mod.ts": "https://deno.land/x/dnt@0.40.0/mod.ts", 33 | "https://deno.land/x/unknownutil/mod.ts": "https://deno.land/x/unknownutil@v3.17.0/mod.ts" 34 | }, 35 | "remote": { 36 | "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", 37 | "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", 38 | "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", 39 | "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", 40 | "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", 41 | "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", 42 | "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", 43 | "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", 44 | "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", 45 | "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", 46 | "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", 47 | "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", 48 | "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", 49 | "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", 50 | "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", 51 | "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", 52 | "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", 53 | "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", 54 | "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", 55 | "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", 56 | "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", 57 | "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", 58 | "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", 59 | "https://deno.land/std@0.181.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", 60 | "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", 61 | "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", 62 | "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", 63 | "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", 64 | "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", 65 | "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", 66 | "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", 67 | "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", 68 | "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", 69 | "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", 70 | "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", 71 | "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", 72 | "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", 73 | "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", 74 | "https://deno.land/x/deno_cache@0.6.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", 75 | "https://deno.land/x/deno_cache@0.6.2/cache.ts": "58b53c128b742757efcad10af9a3871f23b4e200674cb5b0ddf61164fb9b2fe7", 76 | "https://deno.land/x/deno_cache@0.6.2/deno_dir.ts": "1ea355b8ba11c630d076b222b197cfc937dd81e5a4a260938997da99e8ff93a0", 77 | "https://deno.land/x/deno_cache@0.6.2/deps.ts": "12cca94516cf2d3ed42fccd4b721ecd8060679253f077d83057511045b0081aa", 78 | "https://deno.land/x/deno_cache@0.6.2/dirs.ts": "009c6f54e0b610914d6ce9f72f6f6ccfffd2d47a79a19061e0a9eb4253836069", 79 | "https://deno.land/x/deno_cache@0.6.2/disk_cache.ts": "66a1e604a8d564b6dd0500326cac33d08b561d331036bf7272def80f2f7952aa", 80 | "https://deno.land/x/deno_cache@0.6.2/file_fetcher.ts": "4f3e4a2c78a5ca1e4812099e5083f815a8525ab20d389b560b3517f6b1161dd6", 81 | "https://deno.land/x/deno_cache@0.6.2/http_cache.ts": "407135eaf2802809ed373c230d57da7ef8dff923c4abf205410b9b99886491fd", 82 | "https://deno.land/x/deno_cache@0.6.2/lib/deno_cache_dir.generated.js": "59f8defac32e8ebf2a30f7bc77e9d88f0e60098463fb1b75e00b9791a4bbd733", 83 | "https://deno.land/x/deno_cache@0.6.2/lib/snippets/deno_cache_dir-a2aecaa9536c9402/fs.js": "cbe3a976ed63c72c7cb34ef845c27013033a3b11f9d8d3e2c4aa5dda2c0c7af6", 84 | "https://deno.land/x/deno_cache@0.6.2/mod.ts": "b4004287e1c6123d7f07fe9b5b3e94ce6d990c4102949a89c527c68b19627867", 85 | "https://deno.land/x/deno_cache@0.6.2/util.ts": "f3f5a0cfc60051f09162942fb0ee87a0e27b11a12aec4c22076e3006be4cc1e2", 86 | "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", 87 | "https://deno.land/x/dnt@0.40.0/lib/compiler.ts": "7f4447531581896348b8a379ab94730856b42ae50d99043f2468328360293cb1", 88 | "https://deno.land/x/dnt@0.40.0/lib/compiler_transforms.ts": "f21aba052f5dcf0b0595c734450842855c7f572e96165d3d34f8fed2fc1f7ba1", 89 | "https://deno.land/x/dnt@0.40.0/lib/mod.deps.ts": "8d6123c8e1162037e58aa8126686a03d1e2cffb250a8757bf715f80242097597", 90 | "https://deno.land/x/dnt@0.40.0/lib/npm_ignore.ts": "57fbb7e7b935417d225eec586c6aa240288905eb095847d3f6a88e290209df4e", 91 | "https://deno.land/x/dnt@0.40.0/lib/package_json.ts": "607b0a4f44acad071a4c8533b312a27d6671eac8e6a23625c8350ce29eadb2ba", 92 | "https://deno.land/x/dnt@0.40.0/lib/pkg/dnt_wasm.generated.js": "2694546844a50861d6d1610859afbf5130baca4dc6cf304541b7ec2d6d998142", 93 | "https://deno.land/x/dnt@0.40.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "aba69a019a6da6f084898a6c7b903b8b583bc0dbd82bfb338449cf0b5bce58fd", 94 | "https://deno.land/x/dnt@0.40.0/lib/shims.ts": "39e5c141f0315c0faf30b479b53f92b9078d92e1fd67ee34cc60b701d8e68dab", 95 | "https://deno.land/x/dnt@0.40.0/lib/test_runner/get_test_runner_code.ts": "4dc7a73a13b027341c0688df2b29a4ef102f287c126f134c33f69f0339b46968", 96 | "https://deno.land/x/dnt@0.40.0/lib/test_runner/test_runner.ts": "4d0da0500ec427d5f390d9a8d42fb882fbeccc92c92d66b6f2e758606dbd40e6", 97 | "https://deno.land/x/dnt@0.40.0/lib/transform.deps.ts": "2e159661e1c5c650de9a573babe0e319349fe493105157307ec2ad2f6a52c94e", 98 | "https://deno.land/x/dnt@0.40.0/lib/types.ts": "b8e228b2fac44c2ae902fbb73b1689f6ab889915bd66486c8a85c0c24255f5fb", 99 | "https://deno.land/x/dnt@0.40.0/lib/utils.ts": "224f15f33e7226a2fd991e438d0291d7ed8c7889807efa2e1ecb67d2d1db6720", 100 | "https://deno.land/x/dnt@0.40.0/mod.ts": "ae1890fbe592e4797e7dd88c1e270f22b8334878e9bf187c4e11ae75746fe778", 101 | "https://deno.land/x/dnt@0.40.0/transform.ts": "f68743a14cf9bf53bfc9c81073871d69d447a7f9e3453e0447ca2fb78926bb1d", 102 | "https://deno.land/x/ts_morph@20.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", 103 | "https://deno.land/x/ts_morph@20.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", 104 | "https://deno.land/x/ts_morph@20.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", 105 | "https://deno.land/x/ts_morph@20.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", 106 | "https://deno.land/x/ts_morph@20.0.0/common/ts_morph_common.js": "2325f94f61dc5f3f98a1dab366dc93048d11b1433d718b10cfc6ee5a1cfebe8f", 107 | "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", 108 | "https://deno.land/x/unknownutil@v3.17.0/inspect.ts": "33e61bdfed94cd586d66600813b528fa93046a2802d8144277b92f0fa5e5f10e", 109 | "https://deno.land/x/unknownutil@v3.17.0/is.ts": "7080444f66ca46e44bb4ec094caad2ef52654397d72861618cbf4c84a8986df5", 110 | "https://deno.land/x/unknownutil@v3.17.0/is/_deprecated.ts": "d1422e65e8a87d1ab156b236d2bdbbeb17a19c88943e6d9c7453662e773c7e7a", 111 | "https://deno.land/x/unknownutil@v3.17.0/is/annotation.ts": "2df8bb0fb948f8db120ec3a812abeac689fe77f7d977786331e03fe18bc0cebc", 112 | "https://deno.land/x/unknownutil@v3.17.0/is/core.ts": "4f378b19138862373096cd11e82980af4b22bfcc7f9079be60a204820199da07", 113 | "https://deno.land/x/unknownutil@v3.17.0/is/factory.ts": "7dfcc86e113d41963d62cea926599f6980ede701a7dc97b4b63346f279757b38", 114 | "https://deno.land/x/unknownutil@v3.17.0/is/utility.ts": "e1ba67073d3e737fa11a8b08e9c1fec383ea70a654dddf19834626a56d50420f", 115 | "https://deno.land/x/unknownutil@v3.17.0/metadata.ts": "db770390cfc1a7743230da20e87ea1e7cbba313de2612b63735cc290ad179512", 116 | "https://deno.land/x/unknownutil@v3.17.0/mod.ts": "175beb74e096cd52db58f895b208e4a0fb0bad4819f27fc4e13dd4181d03188b", 117 | "https://deno.land/x/unknownutil@v3.17.0/util.ts": "4eb75d6f31d4973d79f588be4fce1944c4dc43a73df4c48342d85c86810ba83f", 118 | "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", 119 | "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63" 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * core/match -- a pattern matching library for JavaScript and TypeScript 3 | * 4 | * Copyright (c) 2024 TANIGUCHI Masaya. All rights reserved. 5 | * This code is licensed under the MIT License. See LICENSE file for more information. 6 | * 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | import type { U, A } from "npm:ts-toolbelt@9.6.0"; 11 | import type { Predicate } from "jsr:@core/unknownutil@3.18.0"; 12 | import { is } from "jsr:@core/unknownutil@3.18.0"; 13 | 14 | type Key = string | number | symbol; 15 | type Entries, Acc extends ([keyof Obj, Obj[keyof Obj]])[] = []> = 16 | Keys extends [infer Key extends keyof Obj, ...infer Rest extends (keyof Obj)[]] ? Entries : Acc; 17 | 18 | export class Placeholder {} 19 | 20 | /** 21 | * AnonymousPlaceholder class that holds a test function. 22 | * 23 | * @template V - Type guard function. 24 | */ 25 | export class AnonymousPlaceholder = Predicate> extends Placeholder { 26 | /** The test function to validate the placeholder value. */ 27 | test?: V; 28 | 29 | /** 30 | * Represents a class constructor 31 | * @param [test] - The test parameter (optional). 32 | */ 33 | constructor(test?: V) { 34 | super(); 35 | this.test = test; 36 | } 37 | } 38 | 39 | /** 40 | * RegularPlaceholder class that holds a name and a test function. 41 | * 42 | * @template T - Name of the placeholder. 43 | * @template V - Type guard function. 44 | */ 45 | export class RegularPlaceholder = Predicate> extends Placeholder { 46 | /** The name of the placeholder. */ 47 | name: T; 48 | /** The test function to validate the placeholder value. */ 49 | test?: V; 50 | 51 | /** 52 | * Represents a class constructor. 53 | * @param name - The name parameter. 54 | * @param [test] - The test parameter (optional). 55 | */ 56 | constructor(name: T, test?: V) { 57 | super(); 58 | this.name = name; 59 | this.test = test; 60 | } 61 | } 62 | 63 | /** 64 | * Represents a template string placeholder with optional regular placeholders. 65 | * @template T - Tuple of regular placeholders. 66 | */ 67 | export class TemplateStringPlaceholder extends Placeholder { 68 | /** The template strings array. */ 69 | strings: TemplateStringsArray; 70 | /** The regular placeholders. */ 71 | placeholders: T; 72 | /** Indicates whether the matching should be greedy or not. */ 73 | greedy: boolean; 74 | 75 | /** 76 | * Creates a new instance of TemplateStringPlaceholder. 77 | * @param greedy - Indicates whether the matching should be greedy or not. 78 | * @param strings - The template strings array. 79 | * @param placeholders - The regular placeholders. 80 | */ 81 | constructor(greedy: boolean, strings: TemplateStringsArray, ...placeholders: T) { 82 | super(); 83 | this.greedy = greedy; 84 | this.strings = strings; 85 | this.placeholders = placeholders; 86 | } 87 | } 88 | 89 | function _placeholder = Predicate>(test?: V): AnonymousPlaceholder; 90 | function _placeholder = Predicate>(name: T, test?: V): RegularPlaceholder; 91 | function _placeholder(strings: TemplateStringsArray, ...placeholders: T): TemplateStringPlaceholder; 92 | function _placeholder[], V extends Predicate = Predicate>( 93 | nameOrStringsOrTest?: T | TemplateStringsArray | V, 94 | ...testOrPlaceholders: [] | [V] | U 95 | ): RegularPlaceholder | TemplateStringPlaceholder | AnonymousPlaceholder { 96 | const isAbstractPlaceholders = 97 | (v: unknown[]): v is U => is.ArrayOf(is.InstanceOf(Placeholder))(v); 98 | const isKey = is.UnionOf([is.String, is.Number, is.Symbol]); 99 | const maybeTypeGuard = 100 | (v: unknown): v is V | undefined => is.UnionOf([is.Function, is.Undefined])(v); 101 | const isTemplateStringsArray = 102 | (v: unknown): v is TemplateStringsArray => is.ArrayOf(is.String)(v); 103 | if (maybeTypeGuard(nameOrStringsOrTest)) { 104 | return new AnonymousPlaceholder(nameOrStringsOrTest); 105 | } else if (isKey(nameOrStringsOrTest)) { 106 | if (maybeTypeGuard(testOrPlaceholders[0])) { 107 | return new RegularPlaceholder(nameOrStringsOrTest, testOrPlaceholders[0]); 108 | } 109 | } else if (isTemplateStringsArray(nameOrStringsOrTest)) { 110 | if (isAbstractPlaceholders(testOrPlaceholders)) { 111 | return new TemplateStringPlaceholder(false, nameOrStringsOrTest, ...testOrPlaceholders); 112 | } 113 | } 114 | throw new Error('Invalid arguments'); 115 | } 116 | 117 | function greedy(strings: TemplateStringsArray, ...placeholders: T): TemplateStringPlaceholder { 118 | return new TemplateStringPlaceholder(true, strings, ...placeholders); 119 | } 120 | _placeholder.greedy = greedy; 121 | 122 | /** 123 | * Represents a placeholder that can be used in pattern matching. 124 | */ 125 | export interface PlaceholderFactory { 126 | /** 127 | * Creates an anonymous placeholder with an optional test function. 128 | * 129 | * @param test - The test function to validate the placeholder value. 130 | * @returns An anonymous placeholder with the specified test function. 131 | */ 132 | = Predicate>(test?: V): AnonymousPlaceholder; 133 | 134 | /** 135 | * Creates a regular placeholder with an optional name and test function. 136 | * 137 | * @param name - The name of the placeholder. 138 | * @param test - The test function to validate the placeholder value. 139 | * @returns A regular placeholder with the specified name and test function. 140 | */ 141 | = Predicate>(name: T, test?: V): RegularPlaceholder; 142 | 143 | /** 144 | * Creates a template string placeholder with multiple regular placeholders. 145 | * 146 | * @param strings - The template strings array. 147 | * @param placeholders - The regular placeholders to be used in the template string. 148 | * @returns A template string placeholder with the specified regular placeholders. 149 | */ 150 | (strings: TemplateStringsArray, ...placeholders: T): TemplateStringPlaceholder; 151 | 152 | /** 153 | * Creates a template string placeholder with greedy matching. 154 | * 155 | * @param strings - The template strings array. 156 | * @param placeholders - The regular placeholders to be used in the template string. 157 | * @returns A template string placeholder with the specified regular placeholders. 158 | */ 159 | greedy(strings: TemplateStringsArray, ...placeholders: T): TemplateStringPlaceholder; 160 | } 161 | 162 | 163 | /** 164 | * A placeholder constant. 165 | */ 166 | export const placeholder: PlaceholderFactory = _placeholder; 167 | 168 | /** 169 | * Result type is a recursive type that represents the result of `match` function. 170 | * This type is a record of keys and values that are matched. 171 | * All the keys are declared as placeholders in `P` and the values are the types of the matched values. 172 | * 173 | * @template P - The pattern to match against. 174 | */ 175 | export type Match

= 176 | P extends RegularPlaceholder> ? 177 | { [v in V]: U } : 178 | P extends TemplateStringPlaceholder ? 179 | MatchTemplateString : 180 | P extends AnonymousPlaceholder ? 181 | never : 182 | P extends Array ? 183 | MatchArrayOrRecord> : 184 | P extends Record ? 185 | MatchArrayOrRecord> : 186 | never; 187 | 188 | type MatchTemplateString = never> = 189 | P extends [RegularPlaceholder>, ...infer Others] ? 190 | A.Equals extends 1 ? 191 | MatchTemplateString>>> : 192 | MatchTemplateString>>> : 193 | P extends [infer T, ...infer Others] ? 194 | MatchTemplateString> : 195 | U.Merge; 196 | 197 | type MatchArrayOrRecord = never> = 198 | P extends [infer V, ...infer Others] ? 199 | MatchArrayOrRecord> : 200 | U.Merge; 201 | 202 | /** 203 | * Returns a type that represents the expected type of the pattern. 204 | * Note that this type is experimental and no gurarantee is provided. 205 | * @template P - The pattern to match against. 206 | * @returns The expected type of the pattern. 207 | **/ 208 | export type Expected

= 209 | P extends RegularPlaceholder> ? 210 | U : 211 | P extends TemplateStringPlaceholder ? 212 | string : 213 | P extends AnonymousPlaceholder ? 214 | unknown : 215 | P extends Array ? 216 | ExpectedArray> : 217 | P extends Record ? 218 | ExpectedRecord> : 219 | P; 220 | 221 | type ExpectedArray = 222 | P extends [infer V, ...infer Rest] ? 223 | ExpectedArray]> : 224 | Acc; 225 | 226 | type ExpectedRecord = never> = 227 | P extends [[infer K extends Key, infer V], ...infer Rest] ? 228 | ExpectedRecord } | Acc> : 229 | U.Merge; 230 | 231 | /** 232 | * Matches a pattern against a target object. 233 | * @param pattern - The pattern to match against. 234 | * @param target - The target object to match. 235 | * @returns The result of the match or undefined if there is no match. 236 | */ 237 | export function match(pattern: T, target: unknown): Match | undefined { 238 | if (is.InstanceOf(AnonymousPlaceholder)(pattern)) { 239 | if (!pattern.test || pattern.test(target)) { 240 | return {} as Match; 241 | } 242 | return undefined; 243 | } 244 | if (is.InstanceOf(RegularPlaceholder)(pattern)) { 245 | if (!pattern.test || pattern.test(target)) { 246 | return { [pattern.name]: target } as Match; 247 | } 248 | return undefined; 249 | } 250 | if (is.InstanceOf(TemplateStringPlaceholder)(pattern)) { 251 | if (typeof target !== 'string') { 252 | return undefined; 253 | } 254 | const sep = pattern.greedy ? '(.*)' : '(.*?)'; 255 | const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 256 | const re = new RegExp(`^${pattern.strings.map(esc).join(sep)}$`); 257 | const m = target.match(re); 258 | let result: Match | undefined; 259 | for (let i = 0; m && i < pattern.placeholders.length; i++) { 260 | const subResult = match(pattern.placeholders[i], m[i+1]); 261 | if (subResult) { 262 | result = { ...(result ?? {} as Match), ...subResult }; 263 | continue; 264 | } 265 | return undefined; 266 | } 267 | return result; 268 | } 269 | if (is.ArrayOf(is.Any)(pattern)) { 270 | if (!(is.Array(target) && pattern.length <= target.length)) { 271 | return undefined; 272 | } 273 | const result = {} as Match; 274 | for (let i = 0; i < pattern.length; i++) { 275 | const subResult = match(pattern[i], target[i]); 276 | if (subResult) { 277 | Object.assign(result, subResult); 278 | continue; 279 | } 280 | return undefined; 281 | } 282 | return result; 283 | } 284 | // pattern should be a direct instance of Object 285 | if (pattern instanceof Object && Object.getPrototypeOf(pattern) === Object.prototype && is.Record(target)) { 286 | const result = {} as Match; 287 | for (const [key, value] of Object.entries(pattern)) { 288 | if (key in target) { 289 | const subResult = match(value, target[key]); 290 | if (subResult) { 291 | Object.assign(result, subResult); 292 | continue; 293 | } 294 | } 295 | return undefined; 296 | } 297 | return result; 298 | } 299 | if (pattern === target) { 300 | return {} as Match; 301 | } 302 | return undefined; 303 | } 304 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![@core/match](https://typography.deno.dev/render?text=%40core%2Fmatch&family=Poiret+One&weight=400&size=50&color=%23ff8040) 2 | 3 | \[ [GitHub tani/ts-match](https://github.com/tani/ts-match) \] \[ [JSR @core/match](https://jsr.io/@core/match) \] 4 | 5 | A pattern matching library for JavaScript/ TypeScript 6 | 7 | EcmaScript has a structured binding. It is a very useful notation for extracting 8 | only the necessary parts from a complex structure. However, this structured 9 | binding is incomplete for use as pattern matching. Because, To do a structured 10 | binding, the pre-assigned value must match the pattern, If it does not match the 11 | pattern, an exception is thrown. 12 | 13 | ```ts 14 | const { a } = JSON.parse("null"); // ERROR! 15 | ``` 16 | 17 | Therefore, to do structured binding, TypeScript either guarantees that the 18 | structure matches at compile time, or it does not, validation libraries such as 19 | zod or unknownutil checks at runtime that the structure matches. The former is 20 | impotent for data whose structure is not determined at compile time, such as 21 | JSON data. The latter required writing two structured binding patterns and two 22 | validation patterns. 23 | 24 | **This library can perform structured binding and validation simultaneously, while 25 | preserving compile-time type information.** It is a library that enables true 26 | pattern matching and brings EcmaScript's structured binding to perfection. 27 | 28 | Again, this is not just for TypeScript, it is also useful in JavaScript. 29 | 30 | ## Usage 31 | 32 | This library is published in JSR and can be used in Deno with `jsr:@core/match`. 33 | There are only two functions that users need to remember: `_` and `match`. 34 | 35 | ```ts 36 | import { match, placeholder as _ } from "jsr:@core/match"; 37 | ``` 38 | 39 | - `_` is a function for creating structured bound patterns. If you already use 40 | `_` for other library, you can use other name like `__`. 41 | 42 | ```ts 43 | const pattern = { 44 | name: _("name"), // this value will be captured as unknown value 45 | address: { 46 | country: _("country"), // you can write the placeholder anywhere,. 47 | state: "NY", // without place holder, matcher will compares the values using === 48 | }, 49 | age: _("age", isNumber), // you can specify the type of placeholder with the type guard, 50 | favorites: ["baseball", _("favorite")], // you can put the placeholder in an array 51 | others: [_(1), _(Symbol.other)], // you can declare the placeholder with number or symbol 52 | message: _`Hello, ${_("nickname")}` // you can put the placeholder in the template string. 53 | }; 54 | ``` 55 | 56 | - `match` is a function for performing structured binding. If you execute a 57 | structured binding based on the above pattern, the value corresponding to the 58 | following `Match` type is: 59 | - an object whose key is the name declared as placeholder, and 60 | - the placeholder given the type guard will be of that type, and 61 | - if the structure does not match or the type guard fails, `undefined` is 62 | returned. 63 | 64 | ```ts 65 | type Match = { 66 | [1]: unknown, 67 | [Symbol.other]: unknown 68 | name: unknown, 69 | country: unknown, 70 | age: number, 71 | favorite: unknown, 72 | nickname: string 73 | } | undefined;. 74 | const result: Match = match(pattern, value); 75 | ``` 76 | 77 | ## How to declare type guards. 78 | 79 | In TypeScript, a type guard is a function with type `(v: unknown) => v is T`, It 80 | can be declared as follows. There is also a collection of generic type guards, 81 | such as unknownutil. 82 | 83 | ```ts 84 | function isNumber(v: unknown): v is number { 85 | return typeof v === "number"; 86 | } 87 | ``` 88 | 89 | ## License. 90 | 91 | This library is licensed under the MIT License. Please feel free to use it as 92 | long as you comply with the licence. 93 | 94 | --- 95 | 96 | ## Scenarios for using this library 97 | 98 | First, load the library. 99 | 100 | ```ts 101 | import { match, placeholder as _ } from "./mod.ts"; 102 | import { assertEquals } from "jsr:@std/assert"; 103 | ``` 104 | 105 | This library can be used to check if an object matches a specific pattern. The 106 | following example demonstrates how to match an object with a simple string 107 | value. If the object matches the pattern, the result will be an empty object, 108 | since the pattern does not contain any placeholders yet. 109 | 110 | ```ts 111 | Deno.test("match object with primitive string value", () => { 112 | const pattern = "hello"; 113 | const value = "hello"; 114 | const result = match(pattern, value); 115 | assertEquals(result, {}); 116 | }); 117 | ``` 118 | 119 | You can also use numeric values as patterns. 120 | 121 | ```ts 122 | Deno.test("match object with primitive number value", () => { 123 | const pattern = 123; 124 | const value = 123; 125 | const result = match(pattern, value); 126 | assertEquals(result, {}); 127 | }); 128 | ``` 129 | 130 | Boolean values also can be used as patterns. 131 | 132 | ```ts 133 | Deno.test("match object with primitive boolean value", () => { 134 | const pattern = true; 135 | const value = true; 136 | const result = match(pattern, value); 137 | assertEquals(result, {}); 138 | }); 139 | ``` 140 | 141 | Null values can be used as patterns as well. Note that the match function 142 | returns an empty object even if both the pattern and the value are null. 143 | 144 | ```ts 145 | Deno.test("match object with primitive null value", () => { 146 | const pattern = null; 147 | const value = null; 148 | const result = match(pattern, value); 149 | assertEquals(result, {}); 150 | }); 151 | ``` 152 | 153 | Undefined values can also be used as patterns. Note that the match function 154 | returns an empty object even if both the pattern and the value are undefined. 155 | 156 | ```ts 157 | Deno.test("match object with primitive undefined value", () => { 158 | const pattern = undefined; 159 | const value = undefined; 160 | const result = match(pattern, value); 161 | assertEquals(result, {}); 162 | }); 163 | ``` 164 | 165 | Symbol values can be used as patterns as well. 166 | 167 | ```ts 168 | Deno.test("match object with primitive symbol value", () => { 169 | const symbol = Symbol("hello"); 170 | const pattern = symbol; 171 | const value = symbol; 172 | const result = match(pattern, value); 173 | assertEquals(result, {}); 174 | }); 175 | ``` 176 | 177 | You can also use compound objects as patterns. If the object matches the 178 | pattern, the result will still be an empty object, because there are no 179 | placeholders in the pattern. 180 | 181 | ```ts 182 | Deno.test("match object with compound object value", () => { 183 | const pattern = { name: "hello", age: 1 }; 184 | const value = { name: "hello", age: 1 }; 185 | const result = match(pattern, value); 186 | assertEquals(result, {}); 187 | }); 188 | ``` 189 | 190 | Arrays can be used as patterns too. If the object matches the pattern, the 191 | result will still be an empty object, because there are no placeholders in the 192 | pattern. 193 | 194 | ```ts 195 | Deno.test("match object with array value", () => { 196 | const pattern = ["hello", 123]; 197 | const value = ["hello", 123]; 198 | const result = match(pattern, value); 199 | assertEquals(result, {}); 200 | }); 201 | ``` 202 | 203 | If the object does not match the pattern, the match function returns undefined. 204 | In the following pattern, the value of the object does not match the pattern 205 | because the value is not object. 206 | 207 | ```ts 208 | Deno.test("match object with primitive value (not equal)", () => { 209 | const pattern = { a: 1 }; 210 | const value = 123; 211 | const result = match(pattern, value); 212 | assertEquals(result, undefined); 213 | }); 214 | ``` 215 | 216 | Now, let's use a placeholder. The first step is to declare a single placeholder. 217 | The placeholder is declared using the `_` function, and the name of the 218 | placeholder is passed as an argument. The placeholder holds the name's string 219 | value and the type guard function `test`. The following placeholder does not 220 | have a type guard, making it the simplest form of a placeholder. 221 | 222 | ```ts 223 | Deno.test("declare single placeholder", () => { 224 | const pattern = _("a"); 225 | assertEquals(pattern.name, "a"); 226 | assertEquals(pattern.test, undefined); 227 | }); 228 | ``` 229 | 230 | To use the placeholder, apply the match function. The match function returns an 231 | object containing the key-value pair of the placeholder's name and the 232 | associated object's value. If the object does not match the pattern, the match 233 | function returns undefined. Note that the resulting object has an `a` key, the 234 | value of the object is `hello`, and its type is `unknown` in TypeScript because 235 | the placeholder has no type guard. 236 | 237 | ```ts 238 | Deno.test("match object with single placeholder", () => { 239 | const pattern = _("a"); 240 | const value = "hello"; 241 | const result = match(pattern, value); 242 | assertEquals(result, { a: "hello" }); 243 | }); 244 | ``` 245 | 246 | To provide a type guard for the placeholder, declare the type guard function as 247 | the second argument of the `_` function. 248 | 249 | ```ts 250 | Deno.test("match object with single placeholder and type guard", () => { 251 | const pattern = _("a", (v: unknown): v is string => typeof v === "string"); 252 | const value = "hello"; 253 | const result = match(pattern, value); 254 | assertEquals(result, { a: "hello" }); 255 | }); 256 | ``` 257 | 258 | If the type guard fails, the match function returns undefined. 259 | 260 | ```ts 261 | Deno.test("match object with single placeholder and type guard", () => { 262 | const pattern = _("a", (v: unknown): v is string => typeof v === "string"); 263 | const value = 123; 264 | const result = match(pattern, value); 265 | assertEquals(result, undefined); 266 | }); 267 | ``` 268 | 269 | Placeholders can also be used in compound objects. The following pattern has two 270 | placeholders, `a` and `b`. Note that the resulting object has `a` and `b` keys, 271 | and the values of the object are `hello` and `123` respectively. Furthermore, 272 | the type of the `a` value is `unknown`, and the type of the `b` value is 273 | `unknown` in TypeScript. 274 | 275 | ```ts 276 | Deno.test("match object with compound object and placeholders", () => { 277 | const pattern = { name: _("a"), age: _("b") }; 278 | const value = { name: "hello", age: 1 }; 279 | const result = match(pattern, value); 280 | assertEquals(result, { a: "hello", b: 1 }); 281 | }); 282 | ``` 283 | 284 | Similarly, you can pass the type guard to the placeholder. In this case, the 285 | type of the `a` value is `string`, while the type of the `b` value is `number` 286 | in TypeScript. 287 | 288 | ```ts 289 | Deno.test("match object with compound object and placeholders (type guard)", () => { 290 | const pattern = { 291 | name: _("a", (v: unknown): v is string => typeof v === "string"), 292 | age: _("b", (v: unknown): v is number => typeof v === "number"), 293 | }; 294 | const value = { name: "hello", age: 1 }; 295 | const result = match(pattern, value); 296 | assertEquals(result, { a: "hello", b: 1 }); 297 | }); 298 | ``` 299 | 300 | It is expected that the match function returns undefined if the object does not 301 | match the pattern. In the following pattern, there are two placeholders, `a` and 302 | `b`. The value of the object does not match the pattern because the `name` key 303 | is missing. Therefore, the match function returns undefined. 304 | 305 | ```ts 306 | Deno.test("match object with compound object and placeholders (missing key)", () => { 307 | const pattern = { name: _("a"), age: _("b") }; 308 | const value = { age: 1 }; 309 | const result = match(pattern, value); 310 | assertEquals(result, undefined); 311 | }); 312 | ``` 313 | 314 | You can also use the placeholder to skip the key. The following pattern has two 315 | placeholders, anonymous placeholder and `b`. Note that the resulting object has 316 | only `b` key, and the value of the object is `1`. Furthermore, the type of the 317 | `b` value is `unknown` in TypeScript because the placeholder has no type guard. 318 | 319 | ```ts 320 | Deno.test("match object with compound object and placeholders (skip key)", () => { 321 | const pattern = { name: _(), age: _("b") }; 322 | const value = { name: "john", age: 1 }; 323 | const result = match(pattern, value); 324 | assertEquals(result, { b: 1 }); 325 | }); 326 | ``` 327 | 328 | As the same as other cases, the anonymous placeholder can be used with the type 329 | guard. The following pattern has two placeholders, anonymous placeholder and 330 | `b`. Note that the resulting object has only `b` key, and the value of the 331 | object is `1`. Furthermore, the type of the `b` value is `unknown` in TypeScript 332 | because the placeholder has no type guard. 333 | 334 | ```ts 335 | Deno.test("match object with compound object and placeholders (skip key, type guard)", () => { 336 | const pattern = { 337 | name: _((v: unknown): v is string => typeof v === "string"), 338 | age: _("b"), 339 | }; 340 | const value = { name: "john", age: 1 }; 341 | const result = match(pattern, value); 342 | assertEquals(result, { b: 1 }); 343 | }); 344 | ``` 345 | 346 | The match function returns undefined if the object does not match the pattern. 347 | The following pattern has two placeholders, anonymous placeholder and `b`. The 348 | value of the object does not match the pattern because the type guard fails. 349 | Therefore, the match function returns undefined. 350 | 351 | ```ts 352 | Deno.test("match object with compound object and placeholders (skip key, type guard, fail)", () => { 353 | const pattern = { 354 | name: _((v: unknown): v is string => typeof v === "string"), 355 | age: _("b"), 356 | }; 357 | const value = { name: 1, age: 1 }; 358 | const result = match(pattern, value); 359 | assertEquals(result, undefined); 360 | }); 361 | ``` 362 | 363 | In other cases, the match function returns undefined if the type guard fails. 364 | The following pattern has two placeholders, `a` and `b`. The value of the object 365 | does not match the pattern because the `age` value is not a number. Therefore, 366 | the match function returns undefined. 367 | 368 | ```ts 369 | Deno.test("match object with compound object and placeholders (type guard fail)", () => { 370 | const pattern = { 371 | name: _("a", (v: unknown): v is string => typeof v === "string"), 372 | age: _("b", (v: unknown): v is number => typeof v === "number"), 373 | }; 374 | const value = { name: "hello", age: "123" }; 375 | const result = match(pattern, value); 376 | assertEquals(result, undefined); 377 | }); 378 | ``` 379 | 380 | Placeholders can be used in arrays as well. The following pattern has two 381 | placeholders, `a` and `b`. Note that the resulting object has `a` and `b` keys, 382 | and the values of the object are `hello` and `123` respectively. Furthermore, 383 | the type of the `a` value is `unknown`, and the type of the `b` value is 384 | `unknown` in TypeScript. 385 | 386 | ```ts 387 | Deno.test("match object with array and placeholders", () => { 388 | const pattern = [_("a"), _("b")]; 389 | const value = ["hello", 123]; 390 | const result = match(pattern, value); 391 | assertEquals(result, { a: "hello", b: 123 }); 392 | }); 393 | ``` 394 | 395 | You can pass the type guard to the placeholder in the same way as before, so the 396 | type of the `a` value is `string`, and the type of the `b` value is `number` in 397 | TypeScript. 398 | 399 | ```ts 400 | Deno.test("match object with array and placeholders (type guard)", () => { 401 | const pattern = [ 402 | _("a", (v: unknown): v is string => typeof v === "string"), 403 | _("b", (v: unknown): v is number => typeof v === "number"), 404 | ]; 405 | const value = ["hello", 123]; 406 | const result = match(pattern, value); 407 | assertEquals(result, { a: "hello", b: 123 }); 408 | }); 409 | ``` 410 | 411 | It is expected that the match function returns undefined if the object does not 412 | match the pattern. The following pattern has two placeholders, `a` and `b`. The 413 | value of the object does not match the pattern because the `name` key is 414 | missing. 415 | 416 | ```ts 417 | Deno.test("match object with array and placeholders (missing key)", () => { 418 | const pattern = [_("a"), _("b")]; 419 | const value = [123]; 420 | const result = match(pattern, value); 421 | assertEquals(result, undefined); 422 | }); 423 | ``` 424 | 425 | In other cases, the match function returns undefined if the type guard fails. 426 | The following pattern has two placeholders, `a` and `b`. The value of the object 427 | does not match the pattern because the `age` value is not a number. 428 | 429 | ```ts 430 | Deno.test("match object with array and placeholders (type guard fail)", () => { 431 | const pattern = [ 432 | _("a", (v: unknown): v is string => typeof v === "string"), 433 | _("b", (v: unknown): v is number => typeof v === "number"), 434 | ]; 435 | const value = ["hello", "123"]; 436 | const result = match(pattern, value); 437 | assertEquals(result, undefined); 438 | }); 439 | ``` 440 | 441 | Placeholders can be used in both objects and arrays at the same time. The 442 | following pattern has two placeholders, `a` and `b`. Note that the resulting 443 | object has `a` and `b` keys, and the values of the object are `hello` and `123` 444 | respectively. Furthermore, the type of the `a` value is `unknown`, and the type 445 | of the `b` value is `unknown` in TypeScript. 446 | 447 | ```ts 448 | Deno.test("match object with object, array, and placeholders", () => { 449 | const pattern = { name: _("a"), age: [_("b")] }; 450 | const value = { name: "hello", age: [123] }; 451 | const result = match(pattern, value); 452 | assertEquals(result, { a: "hello", b: 123 }); 453 | }); 454 | ``` 455 | 456 | In the same manner, you can pass the type guard to the placeholder. Now, the 457 | type of the `a` value is `string`, and the type of the `b` value is `number` in 458 | TypeScript. 459 | 460 | ```ts 461 | Deno.test("match object with object, array, and placeholders (type guard)", () => { 462 | const pattern = { 463 | name: _("a", (v: unknown): v is string => typeof v === "string"), 464 | age: [_("b", (v: unknown): v is number => typeof v === "number")], 465 | }; 466 | const value = { name: "hello", age: [123] }; 467 | const result = match(pattern, value); 468 | assertEquals(result, { a: "hello", b: 123 }); 469 | }); 470 | ``` 471 | 472 | It is expected that the match function will return undefined if the object does 473 | not match the pattern. In the following pattern, there are two placeholders, `a` 474 | and `b`. The value of the object does not match the pattern because the `name` 475 | key is missing. 476 | 477 | ```ts 478 | Deno.test("match object with object, array, and placeholders (missing key)", () => { 479 | const pattern = { name: _("a"), age: [_("b")] }; 480 | const value = { age: [123] }; 481 | const result = match(pattern, value); 482 | assertEquals(result, undefined); 483 | }); 484 | ``` 485 | 486 | In other cases, the match function returns undefined if the type guard fails. 487 | The following pattern has two placeholders, `a` and `b`. The value of the object 488 | does not match the pattern because the `age` value is not a number. 489 | 490 | ```ts 491 | Deno.test("match object with object, array, and placeholders (type guard fail)", () => { 492 | const pattern = { 493 | name: _("a", (v: unknown): v is string => typeof v === "string"), 494 | age: [_("b", (v: unknown): v is number => typeof v === "number")], 495 | }; 496 | const value = { name: "hello", age: ["123"] }; 497 | const result = match(pattern, value); 498 | assertEquals(result, undefined); 499 | }); 500 | ``` 501 | 502 | As a niche feature, pattern matching with placeholders can be used with 503 | user-defined classes. Note that the resulting object has `name` and `age` keys, 504 | and the values of the object are `hello` and `123` respectively. Furthermore, 505 | the type of the `name` value is `unknown`, and the type of the `age` value is 506 | `number` in TypeScript because the placeholder has no type guard for the `name` 507 | value. 508 | 509 | ```ts 510 | class User { 511 | name: string; 512 | age: number; 513 | constructor(name: string, age: number) { 514 | this.name = name; 515 | this.age = age; 516 | } 517 | } 518 | Deno.test("match object with user-defined class and placeholders", () => { 519 | const pattern = { 520 | name: _("name"), 521 | age: _("age", (v: unknown): v is number => typeof v === "number"), 522 | }; 523 | const value = new User("hello", 123); 524 | const result = match(pattern, value); 525 | assertEquals(result, { name: "hello", age: 123 }); 526 | }); 527 | ``` 528 | 529 | In the same manner, you can pass the type guard to the placeholder. Now, the 530 | type of the `name` value is `string`, and the type of the `age` value is 531 | `number` in TypeScript. 532 | 533 | ```ts 534 | Deno.test("match object with user-defined class and placeholders (type guard)", () => { 535 | const pattern = { 536 | name: _("name", (v: unknown): v is string => typeof v === "string"), 537 | age: _("age", (v: unknown): v is number => typeof v === "number"), 538 | }; 539 | const value = new User("hello", 123); 540 | const result = match(pattern, value); 541 | assertEquals(result, { name: "hello", age: 123 }); 542 | }); 543 | ``` 544 | 545 | It is expected that the match function will return undefined if the object does 546 | not match the pattern. In the following pattern, there are two placeholders, 547 | `name` and `age`. The value of the object does not match the pattern because the 548 | `name` key is missing. 549 | 550 | ```ts 551 | Deno.test("match object with user-defined class and placeholders (missing key)", () => { 552 | const pattern = { name: _("name"), age: _("age") }; 553 | const value = new User("hello", 123); 554 | const result = match(pattern, value); 555 | assertEquals(result, { name: "hello", age: 123 }); 556 | }); 557 | ``` 558 | 559 | Additionally, if the pattern is not a direct instance of an `Object` or an 560 | `Array`, the match function tries to check the equality of the pattern and the 561 | value using the `===` operator. Hence, the following pattern does not match the 562 | value, and the match function returns undefined. 563 | 564 | ```ts 565 | Deno.test("match object with primitive string value (not equal)", () => { 566 | const pattern = new User("hello", 123); 567 | const value = new User("hello", 123); 568 | const result = match(pattern, value); 569 | assertEquals(result, undefined); 570 | }); 571 | ``` 572 | 573 | The match function also supports template string placeholders. The following 574 | pattern has a template string placeholder, `name`. Note that the resulting 575 | object has a `name` key, and the value of the object is `john`. The type of the 576 | `name` value is `string` in TypeScript because the placeholder has no type 577 | guard. 578 | 579 | ```ts 580 | Deno.test("match object with template string placeholder", () => { 581 | const pattern = _`hello ${_("name")}`; 582 | const value = "hello john"; 583 | const result = match(pattern, value); 584 | assertEquals(result, { name: "john" }); 585 | }); 586 | ``` 587 | 588 | You can also pass the type guard to the placeholder in the same way as before. 589 | Now, the type of the `name` value is `string` in TypeScript. 590 | 591 | ```ts 592 | Deno.test("match object with template string placeholder (type guard)", () => { 593 | const pattern = _`hello ${ 594 | _("name", (v: unknown): v is string => typeof v === "string") 595 | }`; 596 | const value = "hello john"; 597 | const result = match(pattern, value); 598 | assertEquals(result, { name: "john" }); 599 | }); 600 | ``` 601 | 602 | The match function returns undefined if the object does not match the pattern. 603 | The following pattern has a template string placeholder, `answer`. The value of 604 | the object does not match the pattern because the value is not a number. 605 | 606 | ```ts 607 | Deno.test("match object with template string placeholder (type guard, fail)", () => { 608 | const isNumString = (v: unknown): v is `${number}` => 609 | typeof v === "string" && !isNaN(Number(v)); 610 | const pattern = _`1 + 1 = ${_("answer", isNumString)}`; 611 | const value = "1 + 1 = infinity"; 612 | const result = match(pattern, value); 613 | assertEquals(result, undefined); 614 | }); 615 | ``` 616 | 617 | The positive case of the type guard is also tested. The following pattern has a 618 | template string placeholder, `answer`. The value of the object matches the 619 | pattern, and the match function returns an object containing the key-value pair 620 | of the placeholder's name and the associated object's value. Note that the 621 | resulting object has an `answer` key, and the value of the object is `2`. The 622 | type of the `answer` value is `'${numver}'`, which is a string shape of a number 623 | in TypeScript. 624 | 625 | ```ts 626 | Deno.test("match object with template string placeholder (type guard)", () => { 627 | const isNumString = (v: unknown): v is `${number}` => 628 | typeof v === "string" && !isNaN(Number(v)); 629 | const pattern = _`1 + 1 = ${_("answer", isNumString)}`; 630 | const value = "1 + 1 = 2"; 631 | const result = match(pattern, value); 632 | assertEquals(result, { answer: "2" }); 633 | }); 634 | ``` 635 | 636 | It is expected that the match function will return undefined if the object does 637 | not match the pattern. In the following pattern, there is a template string 638 | placeholder, `name`. The value of the object does not match the pattern because 639 | the length of the value is not equal to the length of the pattern. 640 | 641 | ```ts 642 | Deno.test("match object with template string placeholder (length not match)", () => { 643 | const pattern = _`hello ${_("name")}`; 644 | const value = "hello"; 645 | const result = match(pattern, value); 646 | assertEquals(result, undefined); 647 | }); 648 | ``` 649 | 650 | In other cases, the match function returns undefined. The following pattern has 651 | a template string placeholder, `name`. The value of the object does not match 652 | the pattern because the value is not a string. 653 | 654 | ```ts 655 | Deno.test("match object with template string placeholder (not string)", () => { 656 | const pattern = _`hello ${_("name")}`; 657 | const value = 123; 658 | const result = match(pattern, value); 659 | assertEquals(result, undefined); 660 | }); 661 | ``` 662 | 663 | The match function also supports template string placeholders with multiple 664 | placeholders. The following pattern has two template string placeholders, `name` 665 | and `age`. Note that the resulting object has `name` and `age` keys, and the 666 | values of the object are `john` and `123` respectively. The type of the `name` 667 | value is `string`, and the type of the `age` value is `string` in TypeScript 668 | because the placeholders have no type guards. 669 | 670 | ```ts 671 | Deno.test("match object with template string placeholder with multiple placeholders", () => { 672 | const pattern = _`${_("name")} is ${_("age")} years old`; 673 | const value = "john is 123 years old"; 674 | const result = match(pattern, value); 675 | assertEquals(result, { name: "john", age: "123" }); 676 | }); 677 | ``` 678 | 679 | Match funciton returns undefined if the object does not match the pattern. The 680 | following pattern has two template string placeholders, `name` and `age`. The 681 | value of the object does not match the pattern because the value is shorter than 682 | the pattern. 683 | 684 | ```ts 685 | Deno.test("match object with template string placeholder with multiple placeholders (not equal)", () => { 686 | const pattern = _`${_("name")} is ${_("age")} years old`; 687 | const value = "john is 123 years"; 688 | const result = match(pattern, value); 689 | assertEquals(result, undefined); 690 | }); 691 | ``` 692 | 693 | The match function also supports template string placeholders with greedy mode. 694 | The following pattern has two template string placeholders, `name` and `age`. 695 | Note that the resulting object has `name` and `age` keys, and the values of the 696 | object are `john` and `123` respectively. The type of the `name` value is 697 | `string`, and the type of the `age` value is `string` in TypeScript because 698 | the placeholders have no type guards. 699 | 700 | ```ts 701 | Deno.test("match object with template string placeholder with multiple placeholders (greedy, not equal)", () => { 702 | const pattern = _.greedy`${_("name")} is ${_("age")} years old`; 703 | const value = "john is 123 years old"; 704 | const result = match(pattern, value); 705 | assertEquals(result, { name: "john", age: "123" }); 706 | }); 707 | ``` 708 | 709 | Ultimately, the match function could run with a pattern that contains a regular 710 | placeholder and a template string placeholder. The following pattern has a 711 | regular placeholder, `address`, and a template string placeholder, `message`. 712 | Note that the resulting object has `address`, `name`, and `age` keys, and the 713 | values of the object are `123`, `john`, and `123` respectively. The type of the 714 | `address` value is `string`, and the type of the `name` and `age` values are 715 | `string` in TypeScript because the placeholders have no type guards. 716 | 717 | ```ts 718 | Deno.test("match object with a regular placeholder with the template string placeholder", () => { 719 | const pattern = { 720 | address: _("address"), 721 | message: _`${_("name")} is ${_("age")} years old`, 722 | }; 723 | const value = { 724 | address: "123", 725 | message: "john is 123 years old", 726 | }; 727 | const result = match(pattern, value); 728 | assertEquals(result, { address: "123", name: "john", age: "123" }); 729 | }); 730 | ``` 731 | --------------------------------------------------------------------------------