├── index.js ├── .github ├── dependabot.yaml └── workflows │ └── ci.yml ├── binding.gyp ├── package.json ├── LICENSE ├── README.md ├── benchmark └── benchmark.js ├── test └── test.js ├── .gitignore └── src └── fast-string-search.c /index.js: -------------------------------------------------------------------------------- 1 | const fss = require('bindings')('fast-string-search'); 2 | 3 | module.exports = fss; 4 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "fast-string-search", 5 | "sources": [ "./src/fast-string-search.c" ] 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tests: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: 11 | - ubuntu-latest 12 | - macos-latest 13 | - windows-latest 14 | node-version: 15 | - 16.x 16 | - 18.x 17 | - 20.x 18 | name: Use ${{ matrix.node-version }} on ${{ matrix.os }} 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm install 26 | - run: npm run build --if-present 27 | - run: npm test -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-string-search", 3 | "version": "1.4.4", 4 | "description": "Fast search substrings in a string by using N-API and boyer-moore-magiclen.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "benchmark": "mocha benchmark" 9 | }, 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/magiclen/node-fast-string-search.git" 16 | }, 17 | "keywords": [ 18 | "nodejs", 19 | "stringbuilder", 20 | "Boyer-Moore-MagicLen", 21 | "string", 22 | "search", 23 | "string" 24 | ], 25 | "author": "Magic Len (https://magiclen.org)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/magiclen/node-fast-string-search/issues" 29 | }, 30 | "homepage": "https://magiclen.org/node-js-fast-string-search/", 31 | "dependencies": { 32 | "bindings": "^1.5.0" 33 | }, 34 | "devDependencies": { 35 | "chai": "^4.3.8", 36 | "mocha": "^9.2.2", 37 | "mocha-logger": "^1.0.8" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Magic Len (Xin-rong Li) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fast String Search 2 | ================================= 3 | 4 | [![CI](https://github.com/magiclen/node-fast-string-search/actions/workflows/ci.yml/badge.svg)](https://github.com/magiclen/node-fast-string-search/actions/workflows/ci.yml) 5 | 6 | This module can search substrings in a string by using N-API and boyer-moore-magiclen. The result of benchmark shows that this module is **10 times faster** than the `indexOf` function of a Node.js string. 7 | 8 | ## Initialization 9 | 10 | Import this module by using `require` function. 11 | 12 | ```javascript 13 | const fss = require("fast-string-search"); 14 | ``` 15 | 16 | ## Usage 17 | 18 | ### indexOf 19 | 20 | Full text search in a string. 21 | 22 | ```javascript 23 | const a = fss.indexOf("coocoocoocoo", "oocoo"); // [1, 4, 7] 24 | ``` 25 | 26 | You can also set the offset of characters and the number of substrings you want to find. 27 | 28 | ```javascript 29 | const a = fss.indexOf(source, pattern, offset, limit); 30 | ``` 31 | 32 | The default value of `offset` is `0`, and the default value of `limit` is `1000`. 33 | 34 | ### indexOfSkip 35 | 36 | Normal text search in a string. 37 | 38 | ```javascript 39 | const a = fss.indexOfSkip("coocoocoocoo", "oocoo"); // [1, 7] 40 | ``` 41 | 42 | ### lastIndexOf 43 | 44 | Full text search from the end of a string. 45 | 46 | ```javascript 47 | const a = fss.lastIndexOf("coocoocoocoo", "oocoo"); // [7, 4, 1] 48 | ``` 49 | 50 | ### utf16IndexOf/utf16IndexOfSkip/utf16LastIndexOf 51 | 52 | ```javascript 53 | const a = fss.utf16IndexOf(Buffer.from("coocoocoocoo", "utf16le"), Buffer.from("oocoo", "utf16le")); // [1, 4, 7] 54 | ``` 55 | 56 | ## Tests 57 | 58 | To run the test suite, first install the dependencies, then run `npm test`: 59 | 60 | ```bash 61 | npm install 62 | npm test 63 | ``` 64 | 65 | ## Benchmark 66 | 67 | To run the benchmark suite, first install the dependencies, then run `npm run benchmark`: 68 | 69 | ```bash 70 | npm install 71 | npm run benchmark 72 | ``` 73 | 74 | Here is my result, 75 | 76 | ```bash 77 | Full Text Search 78 | - 87 milliseconds 79 | ✓ natively search text(indexOf) (87ms) 80 | - 7 milliseconds 81 | ✓ Use FSS to search text 82 | 83 | Normal Text Search 84 | - 35 milliseconds 85 | ✓ natively search text(indexOf) 86 | - 46 milliseconds 87 | ✓ natively search text(RegExp) (46ms) 88 | - 6 milliseconds 89 | ✓ Use FSS to search text 90 | ``` 91 | 92 | ## License 93 | 94 | [MIT](LICENSE) 95 | -------------------------------------------------------------------------------- /benchmark/benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const mlog = require('mocha-logger'); 5 | 6 | const fss = require('../index'); 7 | 8 | describe('Full Text Search', function() { 9 | var a = ''; 10 | for (let i = 0; i < 1000000; ++i) { 11 | a += 'COO'; 12 | } 13 | var p = 'OOCOO'; 14 | var startTime, endTime; 15 | 16 | it('natively search text(indexOf)', function() { 17 | var s = a; 18 | startTime = Date.now(); 19 | var sum = 0; 20 | var index; 21 | var offset = 0; 22 | var indexArray = []; 23 | while (true) { 24 | index = s.indexOf(p, offset); 25 | if (index < 0) { 26 | break; 27 | } 28 | indexArray.push(index); 29 | offset = index + 1; 30 | } 31 | var sum = indexArray.length; 32 | endTime = Date.now(); 33 | mlog.log(endTime - startTime, 'milliseconds'); 34 | }); 35 | 36 | it('Use FSS to search text', function() { 37 | startTime = Date.now(); 38 | var indexArray = fss.indexOf(a, p, 0, 1000000); 39 | var sum = indexArray.length; 40 | endTime = Date.now(); 41 | mlog.log(endTime - startTime, 'milliseconds'); 42 | }); 43 | }); 44 | 45 | describe('Normal Text Search', function() { 46 | var a = ''; 47 | for (let i = 0; i < 1000000; ++i) { 48 | a += 'COO'; 49 | } 50 | var p = 'OOCOO'; 51 | var startTime, endTime; 52 | 53 | it('natively search text(indexOf)', function() { 54 | var s = a; 55 | startTime = Date.now(); 56 | var sum = 0; 57 | var index; 58 | var offset = 0; 59 | var indexArray = []; 60 | var pLength = p.length; 61 | while (true) { 62 | index = s.indexOf(p, offset); 63 | if (index < 0) { 64 | break; 65 | } 66 | indexArray.push(index); 67 | offset = index + pLength; 68 | } 69 | var sum = indexArray.length; 70 | endTime = Date.now(); 71 | mlog.log(endTime - startTime, 'milliseconds'); 72 | }); 73 | 74 | it('natively search text(RegExp)', function() { 75 | var s = a; 76 | startTime = Date.now(); 77 | var regex = /OOCOO/g; 78 | var sum = 0; 79 | var index; 80 | var match = 0; 81 | var indexArray = []; 82 | while (match = regex.exec(s)) { 83 | indexArray.push(match.index); 84 | } 85 | var sum = indexArray.length; 86 | endTime = Date.now(); 87 | mlog.log(endTime - startTime, 'milliseconds'); 88 | }); 89 | 90 | it('Use FSS to search text', function() { 91 | startTime = Date.now(); 92 | var indexArray = fss.indexOfSkip(a, p, 0, 500000); 93 | var sum = indexArray.length; 94 | endTime = Date.now(); 95 | mlog.log(endTime - startTime, 'milliseconds'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | 5 | const fss = require('../index'); 6 | 7 | describe('#indexOf', function() { 8 | it('should find two substrings', function() { 9 | var result = fss.indexOf('coocoocoo', 'oocoo').toString(); 10 | expect(result).to.equal('1,4'); 11 | }); 12 | it('should find one substring', function() { 13 | var result = fss.indexOfSkip('coocoocoo', 'oocoo').toString(); 14 | expect(result).to.equal('1'); 15 | }); 16 | it('should find three substrings', function() { 17 | var result = fss.indexOf('coocoocoocoo', 'oocoo').toString(); 18 | expect(result).to.equal('1,4,7'); 19 | }); 20 | it('should find two substrings', function() { 21 | var result = fss.indexOfSkip('coocoocoocoo', 'oocoo').toString(); 22 | expect(result).to.equal('1,7'); 23 | }); 24 | it('should find four substrings', function() { 25 | var result = fss.indexOf('HERE IS A SIMPLE EXAMPLE, WHICH CONTAINS MULTIPLE EXAMPLES. SIXLEE IS A WRONG WORD. EXAMPLEEXAMPLE', 'EXAMPLE').toString(); 26 | expect(result).to.equal('17,50,84,91'); 27 | }); 28 | it('should find four substrings', function() { 29 | var result = fss.indexOfSkip('HERE IS A SIMPLE EXAMPLE, WHICH CONTAINS MULTIPLE EXAMPLES. SIXLEE IS A WRONG WORD. EXAMPLEEXAMPLE', 'EXAMPLE').toString(); 30 | expect(result).to.equal('17,50,84,91'); 31 | }); 32 | }); 33 | 34 | describe('#lastIndexOf', function() { 35 | it('should find two substrings', function() { 36 | var result = fss.lastIndexOf('coocoocoo', 'oocoo').toString(); 37 | expect(result).to.equal('4,1'); 38 | }); 39 | it('should find three substrings', function() { 40 | var result = fss.lastIndexOf('coocoocoocoo', 'oocoo').toString(); 41 | expect(result).to.equal('7,4,1'); 42 | }); 43 | it('should find three substrings', function() { 44 | var result = fss.lastIndexOf('coocoocoocoo', 'oocoo', 'coocoocoocoo'.length - 7 - 'oocoo'.length + 1).toString(); 45 | expect(result).to.equal('4,1'); 46 | }); 47 | it('should find two substrings', function() { 48 | var result = fss.lastIndexOf('coocoocoocoo', 'oocoo', 'coocoocoocoo'.length - 4 - 'oocoo'.length + 1).toString(); 49 | expect(result).to.equal('1'); 50 | }); 51 | }); 52 | 53 | describe('#utf16IndexOf', function() { 54 | it('should find two substrings', function() { 55 | var result = fss.utf16IndexOf(Buffer.from('coocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le')).toString(); 56 | expect(result).to.equal('1,4'); 57 | }); 58 | it('should find one substring', function() { 59 | var result = fss.utf16IndexOfSkip(Buffer.from('coocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le')).toString(); 60 | expect(result).to.equal('1'); 61 | }); 62 | it('should find three substrings', function() { 63 | var result = fss.utf16IndexOf(Buffer.from('coocoocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le')).toString(); 64 | expect(result).to.equal('1,4,7'); 65 | }); 66 | it('should find two substrings', function() { 67 | var result = fss.utf16IndexOfSkip(Buffer.from('coocoocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le')).toString(); 68 | expect(result).to.equal('1,7'); 69 | }); 70 | it('should find four substrings', function() { 71 | var result = fss.utf16IndexOf(Buffer.from('HERE IS A SIMPLE EXAMPLE, WHICH CONTAINS MULTIPLE EXAMPLES. SIXLEE IS A WRONG WORD. EXAMPLEEXAMPLE', 'utf16le'), Buffer.from('EXAMPLE', 'utf16le')).toString(); 72 | expect(result).to.equal('17,50,84,91'); 73 | }); 74 | it('should find four substrings', function() { 75 | var result = fss.utf16IndexOfSkip(Buffer.from('HERE IS A SIMPLE EXAMPLE, WHICH CONTAINS MULTIPLE EXAMPLES. SIXLEE IS A WRONG WORD. EXAMPLEEXAMPLE', 'utf16le'), Buffer.from('EXAMPLE', 'utf16le')).toString(); 76 | expect(result).to.equal('17,50,84,91'); 77 | }); 78 | }); 79 | 80 | describe('#utf16LastIndexOf', function() { 81 | it('should find two substrings', function() { 82 | var result = fss.utf16LastIndexOf(Buffer.from('coocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le')).toString(); 83 | expect(result).to.equal('4,1'); 84 | }); 85 | it('should find three substrings', function() { 86 | var result = fss.utf16LastIndexOf(Buffer.from('coocoocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le')).toString(); 87 | expect(result).to.equal('7,4,1'); 88 | }); 89 | it('should find three substrings', function() { 90 | var result = fss.utf16LastIndexOf(Buffer.from('coocoocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le'), 'coocoocoocoo'.length - 7 - 'oocoo'.length + 1).toString(); 91 | expect(result).to.equal('4,1'); 92 | }); 93 | it('should find two substrings', function() { 94 | var result = fss.utf16LastIndexOf(Buffer.from('coocoocoocoo', 'utf16le'), Buffer.from('oocoo', 'utf16le'), 'coocoocoocoo'.length - 4 - 'oocoo'.length + 1).toString(); 95 | expect(result).to.equal('1'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Node ### 90 | # Logs 91 | logs 92 | *.log 93 | npm-debug.log* 94 | yarn-debug.log* 95 | yarn-error.log* 96 | lerna-debug.log* 97 | .pnpm-debug.log* 98 | 99 | # Diagnostic reports (https://nodejs.org/api/report.html) 100 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 101 | 102 | # Runtime data 103 | pids 104 | *.pid 105 | *.seed 106 | *.pid.lock 107 | 108 | # Directory for instrumented libs generated by jscoverage/JSCover 109 | lib-cov 110 | 111 | # Coverage directory used by tools like istanbul 112 | coverage 113 | *.lcov 114 | 115 | # nyc test coverage 116 | .nyc_output 117 | 118 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 119 | .grunt 120 | 121 | # Bower dependency directory (https://bower.io/) 122 | bower_components 123 | 124 | # node-waf configuration 125 | .lock-wscript 126 | 127 | # Compiled binary addons (https://nodejs.org/api/addons.html) 128 | build/ 129 | 130 | # Dependency directories 131 | node_modules/ 132 | jspm_packages/ 133 | 134 | # Snowpack dependency directory (https://snowpack.dev/) 135 | web_modules/ 136 | 137 | # TypeScript cache 138 | *.tsbuildinfo 139 | 140 | # Optional npm cache directory 141 | .npm 142 | 143 | # Optional eslint cache 144 | .eslintcache 145 | 146 | # Optional stylelint cache 147 | .stylelintcache 148 | 149 | # Microbundle cache 150 | .rpt2_cache/ 151 | .rts2_cache_cjs/ 152 | .rts2_cache_es/ 153 | .rts2_cache_umd/ 154 | 155 | # Optional REPL history 156 | .node_repl_history 157 | 158 | # Output of 'npm pack' 159 | *.tgz 160 | 161 | # Yarn Integrity file 162 | .yarn-integrity 163 | 164 | # dotenv environment variable files 165 | .env 166 | .env.development.local 167 | .env.test.local 168 | .env.production.local 169 | .env.local 170 | 171 | # parcel-bundler cache (https://parceljs.org/) 172 | .cache 173 | .parcel-cache 174 | 175 | # Next.js build output 176 | .next 177 | out 178 | 179 | # Nuxt.js build / generate output 180 | .nuxt 181 | dist 182 | 183 | # Gatsby files 184 | .cache/ 185 | # Comment in the public line in if your project uses Gatsby and not Next.js 186 | # https://nextjs.org/blog/next-9-1#public-directory-support 187 | # public 188 | 189 | # vuepress build output 190 | .vuepress/dist 191 | 192 | # vuepress v2.x temp and cache directory 193 | .temp 194 | 195 | # Docusaurus cache and generated files 196 | .docusaurus 197 | 198 | # Serverless directories 199 | .serverless/ 200 | 201 | # FuseBox cache 202 | .fusebox/ 203 | 204 | # DynamoDB Local files 205 | .dynamodb/ 206 | 207 | # TernJS port file 208 | .tern-port 209 | 210 | # Stores VSCode versions used for testing VSCode extensions 211 | .vscode-test 212 | 213 | # yarn v2 214 | .yarn/cache 215 | .yarn/unplugged 216 | .yarn/build-state.yml 217 | .yarn/install-state.gz 218 | .pnp.* 219 | 220 | ### Node Patch ### 221 | # Serverless Webpack directories 222 | .webpack/ 223 | 224 | # Optional stylelint cache 225 | 226 | # SvelteKit build / generate output 227 | .svelte-kit 228 | 229 | ### Vim ### 230 | # Swap 231 | [._]*.s[a-v][a-z] 232 | !*.svg # comment out if you don't need vector files 233 | [._]*.sw[a-p] 234 | [._]s[a-rt-v][a-z] 235 | [._]ss[a-gi-z] 236 | [._]sw[a-p] 237 | 238 | # Session 239 | Session.vim 240 | Sessionx.vim 241 | 242 | # Temporary 243 | .netrwhist 244 | *~ 245 | # Auto-generated tag files 246 | tags 247 | # Persistent undo 248 | [._]*.un~ 249 | 250 | ### VisualStudioCode ### 251 | .vscode/* 252 | !.vscode/settings.json 253 | !.vscode/tasks.json 254 | !.vscode/launch.json 255 | !.vscode/extensions.json 256 | !.vscode/*.code-snippets 257 | 258 | # Local History for Visual Studio Code 259 | .history/ 260 | 261 | # Built Visual Studio Code Extensions 262 | *.vsix 263 | 264 | ### VisualStudioCode Patch ### 265 | # Ignore all local history of files 266 | .history 267 | .ionide -------------------------------------------------------------------------------- /src/fast-string-search.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | napi_value createEmptyArray(napi_env env){ 6 | napi_value result; 7 | napi_create_array_with_length(env, 0, &result); 8 | return result; 9 | } 10 | 11 | napi_value boyerMooreMagicLen(napi_env env, char16_t* source, int64_t sourceLength, char16_t* pattern, int64_t patternLength, int64_t offset, int64_t limit){ 12 | if (patternLength == 0 || offset < 0 || sourceLength - offset < patternLength) { 13 | return createEmptyArray(env); 14 | } 15 | if(limit <= 0) { 16 | limit = 1000; 17 | } 18 | 19 | uint32_t* buffer; 20 | napi_value arrayBuffer; 21 | napi_create_arraybuffer(env, limit * 4, (void**)(&buffer), &arrayBuffer); 22 | 23 | int64_t sourceLength_dec = sourceLength - 1; 24 | int64_t patternLength_dec = patternLength - 1; 25 | napi_value resultList; 26 | uint32_t resultListLength = 0; 27 | int64_t badCharShiftMap[65536] = { patternLength }; 28 | int64_t i; 29 | for (i = 0; i < patternLength_dec; ++i) { 30 | char16_t index = pattern[i]; 31 | badCharShiftMap[index] = patternLength_dec - i; 32 | } 33 | char16_t specialChar = pattern[patternLength_dec]; 34 | int64_t specialShift = badCharShiftMap[specialChar]; 35 | badCharShiftMap[specialChar] = 0; 36 | int64_t sourcePointer = offset + patternLength_dec; 37 | int64_t patternPointer; 38 | while (sourcePointer < sourceLength) { 39 | patternPointer = patternLength_dec; 40 | while (patternPointer >= 0) { 41 | if (source[sourcePointer] != pattern[patternPointer]) { 42 | break; 43 | } 44 | --sourcePointer; 45 | --patternPointer; 46 | } 47 | int64_t starePointer = sourcePointer; 48 | int64_t goodSuffixLength_inc = patternLength - patternPointer; 49 | sourcePointer += goodSuffixLength_inc; 50 | if (patternPointer < 0) { 51 | buffer[resultListLength++] = starePointer + 1; 52 | if (sourcePointer > sourceLength_dec || resultListLength == limit) { 53 | break; 54 | } else { 55 | sourcePointer += badCharShiftMap[source[sourcePointer]]; 56 | continue; 57 | } 58 | } 59 | int64_t shift1 = (sourcePointer <= sourceLength_dec) ? badCharShiftMap[source[sourcePointer]] : 0; 60 | if (shift1 >= patternLength_dec) { 61 | sourcePointer += shift1; 62 | } else { 63 | int64_t shift2 = ((source[starePointer] == specialChar) ? specialShift : badCharShiftMap[source[starePointer]]) - goodSuffixLength_inc; 64 | sourcePointer += (shift1 >= shift2) ? shift1 : shift2; 65 | } 66 | } 67 | napi_create_typedarray(env, napi_uint32_array, resultListLength, arrayBuffer, 0, &resultList); 68 | return resultList; 69 | } 70 | 71 | napi_value boyerMooreMagicLenSkip(napi_env env, char16_t* source, int64_t sourceLength, char16_t* pattern, int64_t patternLength, int64_t offset, int64_t limit){ 72 | if (patternLength == 0 || offset < 0 || sourceLength - offset < patternLength) { 73 | return createEmptyArray(env); 74 | } 75 | if(limit <= 0) { 76 | limit = 1000; 77 | } 78 | 79 | uint32_t* buffer; 80 | napi_value arrayBuffer; 81 | napi_create_arraybuffer(env, limit * 4, (void**)(&buffer), &arrayBuffer); 82 | 83 | int64_t sourceLength_dec = sourceLength - 1; 84 | int64_t patternLength_dec = patternLength - 1; 85 | napi_value resultList; 86 | uint32_t resultListLength = 0; 87 | int64_t badCharShiftMap[65536] = { patternLength }; 88 | int64_t i; 89 | for (i = 0; i < patternLength_dec; ++i) { 90 | char16_t index = pattern[i]; 91 | badCharShiftMap[index] = patternLength_dec - i; 92 | } 93 | char16_t specialChar = pattern[patternLength_dec]; 94 | int64_t specialShift = badCharShiftMap[specialChar]; 95 | badCharShiftMap[specialChar] = 0; 96 | int64_t sourcePointer = offset + patternLength_dec; 97 | int64_t patternPointer; 98 | while (sourcePointer < sourceLength) { 99 | patternPointer = patternLength_dec; 100 | while (patternPointer >= 0) { 101 | if (source[sourcePointer] != pattern[patternPointer]) { 102 | break; 103 | } 104 | --sourcePointer; 105 | --patternPointer; 106 | } 107 | int64_t starePointer = sourcePointer; 108 | int64_t goodSuffixLength_inc = patternLength - patternPointer; 109 | sourcePointer += goodSuffixLength_inc; 110 | if (patternPointer < 0) { 111 | buffer[resultListLength++] = starePointer + 1; 112 | if (sourcePointer > sourceLength_dec || resultListLength == limit) { 113 | break; 114 | } else { 115 | sourcePointer += patternLength_dec; 116 | continue; 117 | } 118 | } 119 | int64_t shift1 = (sourcePointer <= sourceLength_dec) ? badCharShiftMap[source[sourcePointer]] : 0; 120 | if (shift1 >= patternLength_dec) { 121 | sourcePointer += shift1; 122 | } else { 123 | int64_t shift2 = ((source[starePointer] == specialChar) ? specialShift : badCharShiftMap[source[starePointer]]) - goodSuffixLength_inc; 124 | sourcePointer += (shift1 >= shift2) ? shift1 : shift2; 125 | } 126 | } 127 | napi_create_typedarray(env, napi_uint32_array, resultListLength, arrayBuffer, 0, &resultList); 128 | return resultList; 129 | } 130 | 131 | napi_value boyerMooreMagicLenRev(napi_env env, char16_t* source, int64_t sourceLength, char16_t* pattern, int64_t patternLength, int64_t offset, int64_t limit){ 132 | if (patternLength == 0 || offset < 0 || sourceLength - offset < patternLength) { 133 | return createEmptyArray(env); 134 | } 135 | if(limit <= 0) { 136 | limit = 1000; 137 | } 138 | 139 | uint32_t* buffer; 140 | napi_value arrayBuffer; 141 | napi_create_arraybuffer(env, limit * 4, (void**)(&buffer), &arrayBuffer); 142 | 143 | int64_t sourceLength_dec = sourceLength - 1; 144 | int64_t patternLength_dec = patternLength - 1; 145 | napi_value resultList; 146 | napi_create_array(env, &resultList); 147 | int64_t resultListLength = 0; 148 | int64_t badCharShiftMap[65536] = { patternLength }; 149 | int64_t i; 150 | for (i = patternLength_dec; i > 0; --i) { 151 | char16_t index = pattern[i]; 152 | badCharShiftMap[index] = i; 153 | } 154 | char16_t specialChar = pattern[patternLength_dec]; 155 | int64_t specialShift = badCharShiftMap[specialChar]; 156 | badCharShiftMap[specialChar] = 0; 157 | int64_t sourcePointer = sourceLength_dec - patternLength_dec - offset; 158 | int64_t patternPointer; 159 | while (sourcePointer >= 0) { 160 | patternPointer = 0; 161 | while (patternPointer < patternLength) { 162 | if (source[sourcePointer] != pattern[patternPointer]) { 163 | break; 164 | } 165 | ++sourcePointer; 166 | ++patternPointer; 167 | } 168 | int64_t starePointer = sourcePointer; 169 | int64_t goodSuffixLength_inc = patternPointer + 1; 170 | sourcePointer -= goodSuffixLength_inc; 171 | if (patternPointer >= patternLength) { 172 | buffer[resultListLength++] = sourcePointer + 1; 173 | if (sourcePointer < 0 || resultListLength == limit) { 174 | break; 175 | } else { 176 | sourcePointer -= badCharShiftMap[source[sourcePointer]]; 177 | continue; 178 | } 179 | } 180 | int64_t shift1 = (sourcePointer >= 0) ? badCharShiftMap[source[sourcePointer]] : 0; 181 | if (shift1 >= patternLength_dec) { 182 | sourcePointer -= shift1; 183 | } else { 184 | int64_t shift2 = ((source[starePointer] == specialChar) ? specialShift : badCharShiftMap[source[starePointer]]) - goodSuffixLength_inc; 185 | sourcePointer -= (shift1 >= shift2) ? shift1 : shift2; 186 | } 187 | } 188 | napi_create_typedarray(env, napi_uint32_array, resultListLength, arrayBuffer, 0, &resultList); 189 | return resultList; 190 | } 191 | 192 | napi_value indexOf(napi_env env, napi_callback_info info){ 193 | size_t argsLength = 4; 194 | napi_value args[4]; 195 | napi_get_cb_info(env, info, &argsLength, args, 0, 0); 196 | 197 | if(argsLength < 2) { 198 | return createEmptyArray(env); 199 | } 200 | size_t sourceLength, patternLength; 201 | napi_get_value_string_utf16(env, args[0], NULL, 0, &sourceLength); 202 | napi_get_value_string_utf16(env, args[1], NULL, 0, &patternLength); 203 | 204 | char16_t* source = (char16_t*)malloc(sizeof(char16_t) * (sourceLength + 1)); 205 | char16_t* pattern = (char16_t*)malloc(sizeof(char16_t) * (patternLength + 1)); 206 | napi_get_value_string_utf16(env, args[0], source, sourceLength + 1, &sourceLength); 207 | napi_get_value_string_utf16(env, args[1], pattern, patternLength + 1, &patternLength); 208 | 209 | int64_t offset = 0; 210 | int64_t limit = 0; 211 | if(argsLength >= 4) { 212 | napi_get_value_int64(env, args[2], &offset); 213 | napi_get_value_int64(env, args[3], &limit); 214 | }else if(argsLength == 3) { 215 | napi_get_value_int64(env, args[2], &offset); 216 | } 217 | napi_value result = boyerMooreMagicLen(env, source, sourceLength, pattern, patternLength, offset, limit); 218 | free(source); 219 | free(pattern); 220 | return result; 221 | } 222 | 223 | napi_value indexOfSkip(napi_env env, napi_callback_info info){ 224 | size_t argsLength = 4; 225 | napi_value args[4]; 226 | napi_get_cb_info(env, info, &argsLength, args, 0, 0); 227 | 228 | if(argsLength < 2) { 229 | return createEmptyArray(env); 230 | } 231 | size_t sourceLength, patternLength; 232 | napi_get_value_string_utf16(env, args[0], NULL, 0, &sourceLength); 233 | napi_get_value_string_utf16(env, args[1], NULL, 0, &patternLength); 234 | 235 | char16_t* source = (char16_t*)malloc(sizeof(char16_t) * (sourceLength + 1)); 236 | char16_t* pattern = (char16_t*)malloc(sizeof(char16_t) * (patternLength + 1)); 237 | napi_get_value_string_utf16(env, args[0], source, sourceLength + 1, &sourceLength); 238 | napi_get_value_string_utf16(env, args[1], pattern, patternLength + 1, &patternLength); 239 | 240 | int64_t offset = 0; 241 | int64_t limit = 0; 242 | if(argsLength >= 4) { 243 | napi_get_value_int64(env, args[2], &offset); 244 | napi_get_value_int64(env, args[3], &limit); 245 | }else if(argsLength == 3) { 246 | napi_get_value_int64(env, args[2], &offset); 247 | } 248 | napi_value result = boyerMooreMagicLenSkip(env, source, sourceLength, pattern, patternLength, offset, limit); 249 | free(source); 250 | free(pattern); 251 | return result; 252 | } 253 | 254 | napi_value lastIndexOf(napi_env env, napi_callback_info info){ 255 | size_t argsLength = 4; 256 | napi_value args[4]; 257 | napi_get_cb_info(env, info, &argsLength, args, 0, 0); 258 | 259 | if(argsLength < 2) { 260 | return createEmptyArray(env); 261 | } 262 | size_t sourceLength, patternLength; 263 | napi_get_value_string_utf16(env, args[0], NULL, 0, &sourceLength); 264 | napi_get_value_string_utf16(env, args[1], NULL, 0, &patternLength); 265 | 266 | char16_t* source = (char16_t*)malloc(sizeof(char16_t) * (sourceLength + 1)); 267 | char16_t* pattern = (char16_t*)malloc(sizeof(char16_t) * (patternLength + 1)); 268 | napi_get_value_string_utf16(env, args[0], source, sourceLength + 1, &sourceLength); 269 | napi_get_value_string_utf16(env, args[1], pattern, patternLength + 1, &patternLength); 270 | 271 | int64_t offset = 0; 272 | int64_t limit = 0; 273 | if(argsLength >= 4) { 274 | napi_get_value_int64(env, args[2], &offset); 275 | napi_get_value_int64(env, args[3], &limit); 276 | }else if(argsLength == 3) { 277 | napi_get_value_int64(env, args[2], &offset); 278 | } 279 | napi_value result = boyerMooreMagicLenRev(env, source, sourceLength, pattern, patternLength, offset, limit); 280 | free(source); 281 | free(pattern); 282 | return result; 283 | } 284 | 285 | napi_value utf16IndexOf(napi_env env, napi_callback_info info){ 286 | size_t argsLength = 4; 287 | napi_value args[4]; 288 | napi_get_cb_info(env, info, &argsLength, args, 0, 0); 289 | if(argsLength < 2) { 290 | return createEmptyArray(env); 291 | } 292 | 293 | char* sourceDataChars; 294 | size_t sourceDataLength; 295 | napi_get_buffer_info(env, args[0], (void**)(&sourceDataChars), &sourceDataLength); 296 | 297 | size_t sourceDataChars16Length = sourceDataLength / 2; 298 | char16_t* sourceDataChars16 = (char16_t*)sourceDataChars; 299 | 300 | char* patternDataChars; 301 | size_t patternDataLength; 302 | napi_get_buffer_info(env, args[1], (void**)(&patternDataChars), &patternDataLength); 303 | 304 | size_t patternDataChars16Length = patternDataLength / 2; 305 | char16_t* patternDataChars16 = (char16_t*)patternDataChars; 306 | 307 | int64_t offset = 0; 308 | int64_t limit = 0; 309 | if(argsLength >= 4) { 310 | napi_get_value_int64(env, args[2], &offset); 311 | napi_get_value_int64(env, args[3], &limit); 312 | }else if(argsLength == 3) { 313 | napi_get_value_int64(env, args[2], &offset); 314 | } 315 | napi_value result = boyerMooreMagicLen(env, sourceDataChars16, sourceDataChars16Length, patternDataChars16, patternDataChars16Length, offset, limit); 316 | return result; 317 | } 318 | 319 | napi_value utf16IndexOfSkip(napi_env env, napi_callback_info info){ 320 | size_t argsLength = 4; 321 | napi_value args[4]; 322 | napi_get_cb_info(env, info, &argsLength, args, 0, 0); 323 | if(argsLength < 2) { 324 | return createEmptyArray(env); 325 | } 326 | 327 | char* sourceDataChars; 328 | size_t sourceDataLength; 329 | napi_get_buffer_info(env, args[0], (void**)(&sourceDataChars), &sourceDataLength); 330 | 331 | size_t sourceDataChars16Length = sourceDataLength / 2; 332 | char16_t* sourceDataChars16 = (char16_t*)sourceDataChars; 333 | 334 | char* patternDataChars; 335 | size_t patternDataLength; 336 | napi_get_buffer_info(env, args[1], (void**)(&patternDataChars), &patternDataLength); 337 | 338 | size_t patternDataChars16Length = patternDataLength / 2; 339 | char16_t* patternDataChars16 = (char16_t*)patternDataChars; 340 | 341 | int64_t offset = 0; 342 | int64_t limit = 0; 343 | if(argsLength >= 4) { 344 | napi_get_value_int64(env, args[2], &offset); 345 | napi_get_value_int64(env, args[3], &limit); 346 | }else if(argsLength == 3) { 347 | napi_get_value_int64(env, args[2], &offset); 348 | } 349 | napi_value result = boyerMooreMagicLenSkip(env, sourceDataChars16, sourceDataChars16Length, patternDataChars16, patternDataChars16Length, offset, limit); 350 | return result; 351 | } 352 | 353 | napi_value utf16LastIndexOf(napi_env env, napi_callback_info info){ 354 | size_t argsLength = 4; 355 | napi_value args[4]; 356 | napi_get_cb_info(env, info, &argsLength, args, 0, 0); 357 | 358 | if(argsLength < 2) { 359 | return createEmptyArray(env); 360 | } 361 | 362 | char* sourceDataChars; 363 | size_t sourceDataLength; 364 | napi_get_buffer_info(env, args[0], (void**)(&sourceDataChars), &sourceDataLength); 365 | 366 | size_t sourceDataChars16Length = sourceDataLength / 2; 367 | char16_t* sourceDataChars16 = (char16_t*)sourceDataChars; 368 | 369 | char* patternDataChars; 370 | size_t patternDataLength; 371 | napi_get_buffer_info(env, args[1], (void**)(&patternDataChars), &patternDataLength); 372 | 373 | size_t patternDataChars16Length = patternDataLength / 2; 374 | char16_t* patternDataChars16 = (char16_t*)patternDataChars; 375 | 376 | int64_t offset = 0; 377 | int64_t limit = 0; 378 | if(argsLength >= 4) { 379 | napi_get_value_int64(env, args[2], &offset); 380 | napi_get_value_int64(env, args[3], &limit); 381 | }else if(argsLength == 3) { 382 | napi_get_value_int64(env, args[2], &offset); 383 | } 384 | napi_value result = boyerMooreMagicLenRev(env, sourceDataChars16, sourceDataChars16Length, patternDataChars16, patternDataChars16Length, offset, limit); 385 | return result; 386 | } 387 | 388 | napi_value Init (napi_env env, napi_value exports) { 389 | napi_property_descriptor allDesc[] = { 390 | {"indexOf", 0, indexOf, 0, 0, 0, napi_default, 0}, 391 | {"indexOfSkip", 0, indexOfSkip, 0, 0, 0, napi_default, 0}, 392 | {"utf16IndexOf", 0, utf16IndexOf, 0, 0, 0, napi_default, 0}, 393 | {"utf16IndexOfSkip", 0, utf16IndexOfSkip, 0, 0, 0, napi_default, 0}, 394 | {"lastIndexOf", 0, lastIndexOf, 0, 0, 0, napi_default, 0}, 395 | {"utf16LastIndexOf", 0, utf16LastIndexOf, 0, 0, 0, napi_default, 0} 396 | }; 397 | napi_define_properties(env, exports, 6, allDesc); 398 | return exports; 399 | } 400 | 401 | NAPI_MODULE(fss, Init); 402 | --------------------------------------------------------------------------------