Skip to content

Commit

Permalink
feat!: move to commonjs and mirror streamsearch
Browse files Browse the repository at this point in the history
  • Loading branch information
gurgunday committed Oct 10, 2024
1 parent b4edb65 commit 2bf1ae3
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 157 deletions.
29 changes: 9 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# gmatch ![img.shields.io/bundlephobia/minzip/gmatch](https://img.shields.io/bundlephobia/minzip/gmatch)

streamin**gmatch** lets you search for a pattern in a stream, as fast as JavaScriptly possible.
streamin**gmatch** lets you search for a pattern in a stream as fast as JavaScriptly possible.

Works in the browser. No runtime dependencies. Constant memory usage. [Faster than streamsearch.](#benchmarks)

Expand Down Expand Up @@ -54,31 +54,20 @@ The constructor may throw:

#### Callback Parameters

- `isMatch` (boolean): Indicates whether a match was found.
- `startIndex` (number): The start index of the data that doesn't contain the pattern.
- `endIndex` (number): The end index (exclusive) of the data that doesn't contain the pattern.
- `lookbehindBuffer` (Uint8Array | null): Buffer containing data from previous chunks that might be part of a match.
- `currentBuffer` (Uint8Array | null): The current buffer being processed.

**Note:**

The callback will contain **either** the `lookbehindBuffer` or the `currentBuffer`, not both at the same time.
- `isMatch` (boolean): Indicates whether a match is found.
- `data` (Uint8Array | null): Buffer containing data that is not part of a match.
- `start` (number): The start index of the data that doesn't contain the pattern.
- `end` (number): The end index (exclusive) of the data that doesn't contain the pattern.
- `isSafe` (boolean): Indicates whether it's safe to store a reference to `data` without copying it.

## Usage

```js
import { Match } from "gmatch";

const matcher = new Match(
"example",
(isMatch, startIndex, endIndex, lookbehindBuffer, currentBuffer) => {
if (isMatch) {
console.log(`Match found at index: ${endIndex}`);
} else {
console.log(`Processed ${endIndex - startIndex} bytes`);
}
},
);
const matcher = new Match("example", (isMatch, data, start, end, isSafe) => {
console.log(isMatch, data, start, end, isSafe);
});

matcher.write("Some text with an example in it");
matcher.write(" and more exam");
Expand Down
131 changes: 67 additions & 64 deletions bench/index.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,77 @@
/* eslint-disable unicorn/no-array-push-push */
import { Match } from "../src/index.js";
import StreamSearch from "streamsearch";
import { Bench } from "tinybench";
import { Buffer } from "node:buffer";
"use strict";

const bench = new Bench({ time: 5000 });
const pattern = "exampleexampleexampleexampleexampleexample";
const longText =
`This is a long text with multiple occurrences of the word example. ` +
`It repeats the word exampleexampleexampleexampleexampleexample several times to ensure we have enough ${"data for the benchmark. Here's another exampleexampleexampleexampleexampleexample.".repeat(
5,
)}`;
let gmatchMatches;
let streamsearchMatches;
const { Match } = require("../src/index.js");
const StreamSearch = require("streamsearch");
const { Bench } = require("tinybench");

bench
.add("gmatch", () => {
const matches = [];
const search = new Match(
pattern,
(isMatch, data, start) => {
(async () => {
const bench = new Bench({ time: 5000 });
const pattern = "exampleexampleexampleexampleexampleexample";
const longText =
`This is a long text with multiple occurrences of the word example. ` +
`It repeats the word exampleexampleexampleexampleexampleexample several times to ensure we have enough ${"data for the benchmark. Here's another exampleexampleexampleexampleexampleexample.".repeat(
5,
)}`;
let gmatchMatches;
let streamsearchMatches;

bench
.add("gmatch", () => {
const matches = [];
const search = new Match(
pattern,
(isMatch, data, start, end) => {
if (isMatch) {
matches.push(end);
}
},
Buffer.from,
);

search.write("exampleexampleexampleexample");
search.write("fexampleexampleexampleexampleexample");
search.write(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.write("exampleexampleexampleexample");
search.write("fexampleexampleexampleexampleexample");
search.write(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.write(longText);
search.write(pattern);

gmatchMatches = matches.length;
})
.add("streamsearch", () => {
const matches = [];
const search = new StreamSearch(pattern, (isMatch, data, start, end) => {
if (isMatch) {
matches.push(start);
matches.push(end);
}
},
Buffer.from,
);
});

search.write("exampleexampleexampleexample");
search.write("fexampleexampleexampleexampleexample");
search.write(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.write("exampleexampleexampleexample");
search.write("fexampleexampleexampleexampleexample");
search.write(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.write(longText);
search.write(pattern);
search.push("exampleexampleexampleexample");
search.push("fexampleexampleexampleexampleexample");
search.push(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.push("exampleexampleexampleexample");
search.push("fexampleexampleexampleexampleexample");
search.push(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.push(longText);
search.push(pattern);

gmatchMatches = matches.length;
})
.add("streamsearch", () => {
const matches = [];
const search = new StreamSearch(pattern, (isMatch, data, start) => {
if (isMatch) {
matches.push(start);
}
streamsearchMatches = matches.length;
});

search.push("exampleexampleexampleexample");
search.push("fexampleexampleexampleexampleexample");
search.push(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.push("exampleexampleexampleexample");
search.push("fexampleexampleexampleexampleexample");
search.push(
"exampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexampleexample",
);
search.push(longText);
search.push(pattern);

streamsearchMatches = matches.length;
});

await bench.warmup();
await bench.run();
await bench.warmup();
await bench.run();

globalThis.console.table(bench.table());
globalThis.console.log("gmatch matches:", gmatchMatches);
globalThis.console.log("streamsearch matches:", streamsearchMatches);
console.table(bench.table());
console.log("gmatch matches:", gmatchMatches);
console.log("streamsearch matches:", streamsearchMatches);
})();
3 changes: 0 additions & 3 deletions eslint.config.js

This file was deleted.

13 changes: 13 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import grules from "grules";
import globals from "globals";

export default [
...grules,
{
languageOptions: {
globals: {
...globals.node,
},
},
},
];
Binary file modified gmatch.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "gmatch",
"description": "Search for a pattern in a stream, as fast as JavaScriptly possible.",
"description": "Search for a pattern in a stream as fast as JavaScriptly possible.",
"author": "Gürgün Dayıoğlu",
"license": "MIT",
"version": "3.0.5",
"type": "module",
"type": "commonjs",
"main": "./src/index.js",
"exports": "./src/index.js",
"engines": {
Expand All @@ -20,6 +20,7 @@
"devDependencies": {
"@fastify/pre-commit": "^2.1.0",
"c8": "^10.1.2",
"globals": "^15.11.0",
"grules": "^0.25.5",
"streamsearch": "^1.1.0",
"tinybench": "^2.9.0",
Expand Down
26 changes: 15 additions & 11 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const bufferTextEncoder = new globalThis.TextEncoder();
"use strict";

const bufferTextEncoder = new TextEncoder();

const bufferFrom = (string) => {
return bufferTextEncoder.encode(string);
Expand All @@ -14,7 +16,7 @@ const bufferCompare = (buffer1, offset1, buffer2, offset2, length) => {
return true;
};

export const Match = class {
const Match = class {
#from;
#callback;
#pattern;
Expand Down Expand Up @@ -66,7 +68,7 @@ export const Match = class {

destroy() {
if (this.#lookbehindSize) {
this.#callback(false, 0, this.#lookbehindSize, this.#lookbehind, null);
this.#callback(false, this.#lookbehind, 0, this.#lookbehindSize, false);
}

this.reset();
Expand Down Expand Up @@ -110,14 +112,14 @@ export const Match = class {
++this.#matches;

if (-position === this.#lookbehindSize) {
this.#callback(true, 0, 0, null, null);
this.#callback(true, null, 0, 0, false);
} else {
this.#callback(
true,
this.#lookbehind,
0,
position + this.#lookbehindSize,
this.#lookbehind,
null,
false,
);
}

Expand All @@ -133,7 +135,7 @@ export const Match = class {
const bytesToCutOff = position + this.#lookbehindSize;

if (bytesToCutOff) {
this.#callback(false, 0, bytesToCutOff, this.#lookbehind, null);
this.#callback(false, this.#lookbehind, 0, bytesToCutOff, false);
this.#lookbehindSize -= bytesToCutOff;
this.#lookbehind.set(
this.#lookbehind.subarray(bytesToCutOff, this.#lookbehindSize),
Expand All @@ -146,7 +148,7 @@ export const Match = class {
return buffer.length;
}

this.#callback(false, 0, this.#lookbehindSize, this.#lookbehind, null);
this.#callback(false, this.#lookbehind, 0, this.#lookbehindSize, false);
this.#lookbehindSize = 0;
}

Expand All @@ -162,9 +164,9 @@ export const Match = class {
++this.#matches;

if (!position) {
this.#callback(true, 0, 0, null, null);
this.#callback(true, null, 0, 0, false);
} else {
this.#callback(true, offset, position, null, buffer);
this.#callback(true, buffer, offset, position, true);
}

return position + this.#pattern.length;
Expand All @@ -174,7 +176,7 @@ export const Match = class {
}

if (position !== offset) {
this.#callback(false, offset, position, null, buffer);
this.#callback(false, buffer, offset, position, true);
}

if (position !== buffer.length) {
Expand Down Expand Up @@ -212,3 +214,5 @@ export const Match = class {
return table;
};
};

module.exports.Match = Match;
Loading

0 comments on commit 2bf1ae3

Please sign in to comment.