@pipedog/sentencex-wasm 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/LICENSE-sentencex +21 -0
- package/NOTICE +9 -0
- package/README.md +129 -2
- package/THIRD_PARTY_LICENSES.md +1060 -0
- package/dist/api.d.ts +11 -0
- package/dist/api.js +24 -0
- package/dist/engine.d.ts +42 -0
- package/dist/engine.js +105 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +49 -0
- package/dist/wasm_bytes.d.ts +5 -0
- package/dist/wasm_bytes.js +9 -0
- package/dist/workerd.d.ts +13 -0
- package/dist/workerd.js +26 -0
- package/package.json +48 -3
- package/rust/Cargo.lock +312 -0
- package/rust/Cargo.toml +27 -0
- package/rust/src/lib.rs +195 -0
- package/wasm/sentencex.wasm +0 -0
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The public functions, shared by both entries; each entry supplies its own module loader. */
|
|
2
|
+
import { type SentenceBoundary, SentencexEngine } from "./engine.ts";
|
|
3
|
+
export interface SentencexApi {
|
|
4
|
+
segment(language: string, text: string): string[];
|
|
5
|
+
getSentenceBoundaries(language: string, text: string): SentenceBoundary[];
|
|
6
|
+
initSentencex(source?: WebAssembly.Module | BufferSource): SentencexEngine;
|
|
7
|
+
getSentencex(): SentencexEngine;
|
|
8
|
+
}
|
|
9
|
+
/** @internal */
|
|
10
|
+
export declare function createApi(loadDefault: () => WebAssembly.Module): SentencexApi;
|
|
11
|
+
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) 2026, pipedog contributors
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
/** The public functions, shared by both entries; each entry supplies its own module loader. */
|
|
4
|
+
import { compileSync, SentencexEngine } from "./engine.js";
|
|
5
|
+
/** @internal */
|
|
6
|
+
export function createApi(loadDefault) {
|
|
7
|
+
let engine = null;
|
|
8
|
+
const getSentencex = () => {
|
|
9
|
+
engine ??= new SentencexEngine(loadDefault());
|
|
10
|
+
return engine;
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
segment: (language, text) => getSentencex().segment(language, text),
|
|
14
|
+
getSentenceBoundaries: (language, text) => getSentencex().getSentenceBoundaries(language, text),
|
|
15
|
+
initSentencex(source) {
|
|
16
|
+
if (source === undefined)
|
|
17
|
+
return getSentencex();
|
|
18
|
+
engine = new SentencexEngine(compileSync(source));
|
|
19
|
+
return engine;
|
|
20
|
+
},
|
|
21
|
+
getSentencex,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=api.js.map
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written wrapper over the sentencex WebAssembly exports.
|
|
3
|
+
*
|
|
4
|
+
* The module has no imports and a tiny C ABI (see `rust/src/lib.rs`), so there
|
|
5
|
+
* is no generated glue: we copy the UTF-8 language code and text into linear
|
|
6
|
+
* memory, call `sx_segment` / `sx_boundaries`, and read back u32 records of
|
|
7
|
+
* UTF-16 offsets that index the caller's original string. Everything here is
|
|
8
|
+
* synchronous.
|
|
9
|
+
*/
|
|
10
|
+
/** One sentence boundary, as in the `sentencex` npm package's typings. */
|
|
11
|
+
export interface SentenceBoundary {
|
|
12
|
+
/** UTF-16 index (JavaScript string index) where the sentence starts. */
|
|
13
|
+
start_index: number;
|
|
14
|
+
/** UTF-16 index (JavaScript string index) where the sentence ends (exclusive). */
|
|
15
|
+
end_index: number;
|
|
16
|
+
/** The sentence text. */
|
|
17
|
+
text: string;
|
|
18
|
+
/** Punctuation mark that ended the sentence, or null if none. */
|
|
19
|
+
boundary_symbol: string | null;
|
|
20
|
+
/** Whether this boundary represents a paragraph break ("\n\n"). */
|
|
21
|
+
is_paragraph_break: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One instantiated sentencex module. All methods are synchronous.
|
|
25
|
+
*
|
|
26
|
+
* If the WASM code traps (a Rust panic), the instance is discarded and a fresh
|
|
27
|
+
* one is created from the same module on the next call.
|
|
28
|
+
*/
|
|
29
|
+
export declare class SentencexEngine {
|
|
30
|
+
#private;
|
|
31
|
+
readonly module: WebAssembly.Module;
|
|
32
|
+
constructor(module: WebAssembly.Module);
|
|
33
|
+
/** Current size of the instance's linear memory, in bytes (it grows, never shrinks). */
|
|
34
|
+
get memoryBytes(): number;
|
|
35
|
+
/** Split `text` into sentences. Concatenating the result gives back `text`. */
|
|
36
|
+
segment(language: string, text: string): string[];
|
|
37
|
+
/** Detailed sentence boundaries, with JavaScript (UTF-16) string indices. */
|
|
38
|
+
getSentenceBoundaries(language: string, text: string): SentenceBoundary[];
|
|
39
|
+
}
|
|
40
|
+
/** Compile WASM bytes synchronously (not allowed on Cloudflare Workers). */
|
|
41
|
+
export declare function compileSync(source: WebAssembly.Module | BufferSource): WebAssembly.Module;
|
|
42
|
+
//# sourceMappingURL=engine.d.ts.map
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright (c) 2026, pipedog contributors
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
const BOUNDARY_FIELDS = 7;
|
|
4
|
+
const NONE = 0xffffffff;
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
function instantiate(module) {
|
|
8
|
+
const x = new WebAssembly.Instance(module, {}).exports;
|
|
9
|
+
if (typeof x.sx_segment !== "function") {
|
|
10
|
+
throw new Error("not the @pipedog/sentencex-wasm module");
|
|
11
|
+
}
|
|
12
|
+
return x;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* One instantiated sentencex module. All methods are synchronous.
|
|
16
|
+
*
|
|
17
|
+
* If the WASM code traps (a Rust panic), the instance is discarded and a fresh
|
|
18
|
+
* one is created from the same module on the next call.
|
|
19
|
+
*/
|
|
20
|
+
export class SentencexEngine {
|
|
21
|
+
module;
|
|
22
|
+
#x;
|
|
23
|
+
constructor(module) {
|
|
24
|
+
this.module = module;
|
|
25
|
+
this.#x = instantiate(module);
|
|
26
|
+
}
|
|
27
|
+
/** Current size of the instance's linear memory, in bytes (it grows, never shrinks). */
|
|
28
|
+
get memoryBytes() {
|
|
29
|
+
return this.#x.memory.buffer.byteLength;
|
|
30
|
+
}
|
|
31
|
+
/** Split `text` into sentences. Concatenating the result gives back `text`. */
|
|
32
|
+
segment(language, text) {
|
|
33
|
+
return this.#call(language, text, false, (x, n) => {
|
|
34
|
+
const rec = new Uint32Array(x.memory.buffer, x.sx_out(), n * 2);
|
|
35
|
+
const out = new Array(n);
|
|
36
|
+
for (let i = 0; i < n; i++)
|
|
37
|
+
out[i] = text.slice(rec[2 * i], rec[2 * i + 1]);
|
|
38
|
+
return out;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Detailed sentence boundaries, with JavaScript (UTF-16) string indices. */
|
|
42
|
+
getSentenceBoundaries(language, text) {
|
|
43
|
+
return this.#call(language, text, true, (x, n) => {
|
|
44
|
+
const rec = new Uint32Array(x.memory.buffer, x.sx_out(), n * BOUNDARY_FIELDS);
|
|
45
|
+
const bytes = new Uint8Array(x.memory.buffer);
|
|
46
|
+
const out = new Array(n);
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const r = i * BOUNDARY_FIELDS;
|
|
49
|
+
const symLen = rec[r + 5];
|
|
50
|
+
out[i] = {
|
|
51
|
+
start_index: rec[r],
|
|
52
|
+
end_index: rec[r + 1],
|
|
53
|
+
text: text.slice(rec[r + 2], rec[r + 3]),
|
|
54
|
+
boundary_symbol: symLen === NONE
|
|
55
|
+
? null
|
|
56
|
+
: decoder.decode(bytes.subarray(rec[r + 4], rec[r + 4] + symLen)),
|
|
57
|
+
is_paragraph_break: rec[r + 6] !== 0,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
#call(language, text, boundaries, read) {
|
|
64
|
+
if (typeof language !== "string" || typeof text !== "string") {
|
|
65
|
+
throw new TypeError("language and text must be strings");
|
|
66
|
+
}
|
|
67
|
+
const x = this.#x;
|
|
68
|
+
// UTF-8 needs at most 3 bytes per UTF-16 code unit.
|
|
69
|
+
const langCap = language.length * 3;
|
|
70
|
+
const cap = langCap + text.length * 3;
|
|
71
|
+
let ptr = 0;
|
|
72
|
+
try {
|
|
73
|
+
ptr = x.sx_alloc(cap);
|
|
74
|
+
// Views are created after the alloc: a memory.grow detaches the old buffer.
|
|
75
|
+
const mem = new Uint8Array(x.memory.buffer);
|
|
76
|
+
const lang = encoder.encodeInto(language, mem.subarray(ptr, ptr + langCap)).written;
|
|
77
|
+
const textPtr = ptr + langCap;
|
|
78
|
+
const textLen = encoder.encodeInto(text, mem.subarray(textPtr, ptr + cap)).written;
|
|
79
|
+
const n = boundaries
|
|
80
|
+
? x.sx_boundaries(ptr, lang, textPtr, textLen)
|
|
81
|
+
: x.sx_segment(ptr, lang, textPtr, textLen);
|
|
82
|
+
if (n < 0)
|
|
83
|
+
throw new Error(`sentencex failed (code ${n})`);
|
|
84
|
+
// Read before freeing: boundary symbols point into the input buffer.
|
|
85
|
+
const result = read(x, n);
|
|
86
|
+
x.sx_free(ptr, cap);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
if (e instanceof WebAssembly.RuntimeError) {
|
|
91
|
+
// A trap leaves the instance in an unknown state; start over next time.
|
|
92
|
+
this.#x = instantiate(this.module);
|
|
93
|
+
}
|
|
94
|
+
else if (ptr !== 0) {
|
|
95
|
+
x.sx_free(ptr, cap);
|
|
96
|
+
}
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Compile WASM bytes synchronously (not allowed on Cloudflare Workers). */
|
|
102
|
+
export function compileSync(source) {
|
|
103
|
+
return source instanceof WebAssembly.Module ? source : new WebAssembly.Module(source);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=engine.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { SentencexApi } from "./api.ts";
|
|
2
|
+
export { type SentenceBoundary, SentencexEngine } from "./engine.ts";
|
|
3
|
+
/** Split `text` into sentences (sentencex rules for `language`, e.g. "en"). */
|
|
4
|
+
export declare const segment: (language: string, text: string) => string[];
|
|
5
|
+
/** Detailed sentence boundaries with JavaScript (UTF-16) string indices. */
|
|
6
|
+
export declare const getSentenceBoundaries: (language: string, text: string) => import("./engine.ts").SentenceBoundary[];
|
|
7
|
+
/** Alias with the `sentencex` npm package's name. */
|
|
8
|
+
export declare const get_sentence_boundaries: (language: string, text: string) => import("./engine.ts").SentenceBoundary[];
|
|
9
|
+
/**
|
|
10
|
+
* Compile and instantiate now (otherwise done on first use). Passing a
|
|
11
|
+
* `WebAssembly.Module` or bytes replaces the bundled module.
|
|
12
|
+
*/
|
|
13
|
+
export declare const initSentencex: (source?: WebAssembly.Module | BufferSource) => import("./engine.ts").SentencexEngine;
|
|
14
|
+
/** The shared engine, instantiated on first use. */
|
|
15
|
+
export declare const getSentencex: () => import("./engine.ts").SentencexEngine;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Copyright (c) 2026, pipedog contributors
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
/**
|
|
4
|
+
* Default entry (Bun, Node >= 22, Deno, and bundlers).
|
|
5
|
+
*
|
|
6
|
+
* Fully synchronous: the WASM bytes are embedded in `wasm_bytes.ts` (base64)
|
|
7
|
+
* and compiled with `new WebAssembly.Module()` on the first call.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { segment } from "@pipedog/sentencex-wasm";
|
|
12
|
+
* segment("en", "Dr. Smith arrived at 5 p.m. He was late.");
|
|
13
|
+
* // ["Dr. Smith arrived at 5 p.m. ", "He was late."]
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* On Cloudflare Workers the package resolves to `workerd.ts` instead (the
|
|
17
|
+
* "workerd" export condition), which imports the .wasm as a module because
|
|
18
|
+
* workerd cannot compile WASM from bytes.
|
|
19
|
+
*/
|
|
20
|
+
import { createApi } from "./api.js";
|
|
21
|
+
import { compileSync } from "./engine.js";
|
|
22
|
+
import { WASM_BASE64 } from "./wasm_bytes.js";
|
|
23
|
+
export { SentencexEngine } from "./engine.js";
|
|
24
|
+
function decodeBase64(b64) {
|
|
25
|
+
const fromBase64 = Uint8Array
|
|
26
|
+
.fromBase64;
|
|
27
|
+
if (typeof fromBase64 === "function")
|
|
28
|
+
return fromBase64(b64);
|
|
29
|
+
const bin = atob(b64);
|
|
30
|
+
const out = new Uint8Array(bin.length);
|
|
31
|
+
for (let i = 0; i < bin.length; i++)
|
|
32
|
+
out[i] = bin.charCodeAt(i);
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
const api = createApi(() => compileSync(decodeBase64(WASM_BASE64)));
|
|
36
|
+
/** Split `text` into sentences (sentencex rules for `language`, e.g. "en"). */
|
|
37
|
+
export const segment = api.segment;
|
|
38
|
+
/** Detailed sentence boundaries with JavaScript (UTF-16) string indices. */
|
|
39
|
+
export const getSentenceBoundaries = api.getSentenceBoundaries;
|
|
40
|
+
/** Alias with the `sentencex` npm package's name. */
|
|
41
|
+
export const get_sentence_boundaries = api.getSentenceBoundaries;
|
|
42
|
+
/**
|
|
43
|
+
* Compile and instantiate now (otherwise done on first use). Passing a
|
|
44
|
+
* `WebAssembly.Module` or bytes replaces the bundled module.
|
|
45
|
+
*/
|
|
46
|
+
export const initSentencex = api.initSentencex;
|
|
47
|
+
/** The shared engine, instantiated on first use. */
|
|
48
|
+
export const getSentencex = api.getSentencex;
|
|
49
|
+
//# sourceMappingURL=index.js.map
|