nl2time 0.1.1 → 0.3.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/README.md +35 -2
- package/dist/corpus/runner.d.ts +3 -0
- package/dist/corpus/runner.js +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/packs/index.d.ts +39 -0
- package/dist/packs/index.js +156 -0
- package/dist/parse/de.d.ts +9 -0
- package/dist/parse/de.js +1301 -0
- package/dist/parse/en.d.ts +9 -0
- package/dist/parse/en.js +55 -30
- package/dist/parse/es.d.ts +9 -0
- package/dist/parse/es.js +3286 -0
- package/dist/parse/fr.d.ts +9 -0
- package/dist/parse/fr.js +2358 -0
- package/dist/parse/index.d.ts +16 -1
- package/dist/parse/index.js +42 -11
- package/dist/parse/ja.d.ts +7 -0
- package/dist/parse/ja.js +1207 -0
- package/dist/parse/languages.d.ts +17 -0
- package/dist/parse/languages.js +25 -0
- package/dist/parse/latin.d.ts +97 -0
- package/dist/parse/latin.js +692 -0
- package/dist/parse/tokenizer.js +30 -1
- package/dist/parse/zh.d.ts +6 -0
- package/dist/parse/zh.js +1360 -0
- package/package.json +3 -3
package/dist/parse/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { TimeContext } from '../context.js';
|
|
7
7
|
import type { TimeExpr } from '../ir/types.js';
|
|
8
|
+
import { type DomainPack } from '../packs/index.js';
|
|
9
|
+
import { type Rule } from './en.js';
|
|
8
10
|
export interface ParseMatch {
|
|
9
11
|
/** The matched source text span. */
|
|
10
12
|
text: string;
|
|
@@ -17,4 +19,17 @@ export interface ParseMatch {
|
|
|
17
19
|
export interface ParseResult {
|
|
18
20
|
matches: ParseMatch[];
|
|
19
21
|
}
|
|
20
|
-
export
|
|
22
|
+
export interface ParseOptions {
|
|
23
|
+
/** Declarative domain packs (docs/extending.md); their rules run first. */
|
|
24
|
+
packs?: DomainPack[];
|
|
25
|
+
/** Extra code-level rules; run after packs, before built-ins. */
|
|
26
|
+
rules?: Rule[];
|
|
27
|
+
/** Built-in rule names to disable (see EN_RULE_ENTRIES). */
|
|
28
|
+
disable?: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Precompiled parser for hot paths — compiles packs/rule lists once per
|
|
32
|
+
* language instead of per call.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createParser(opts?: ParseOptions): (text: string, ctx: TimeContext) => ParseResult;
|
|
35
|
+
export declare function parse(text: string, ctx: TimeContext, opts?: ParseOptions): ParseResult;
|
package/dist/parse/index.js
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compilePack } from '../packs/index.js';
|
|
2
|
+
import { languageDef } from './languages.js';
|
|
2
3
|
import { tokenize } from './tokenizer.js';
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
function buildRules(opts, language) {
|
|
5
|
+
const base = languageDef(language).ruleEntries;
|
|
6
|
+
if (!opts || (!opts.packs?.length && !opts.rules?.length && !opts.disable?.length)) {
|
|
7
|
+
return base.map((e) => e.rule);
|
|
8
|
+
}
|
|
9
|
+
const disabled = new Set([...(opts.disable ?? []), ...(opts.packs ?? []).flatMap((p) => p.disable ?? [])]);
|
|
10
|
+
return [
|
|
11
|
+
...(opts.packs ?? []).map(compilePack),
|
|
12
|
+
...(opts.rules ?? []),
|
|
13
|
+
...base.filter((e) => !disabled.has(e.name)).map((e) => e.rule),
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Precompiled parser for hot paths — compiles packs/rule lists once per
|
|
18
|
+
* language instead of per call.
|
|
19
|
+
*/
|
|
20
|
+
export function createParser(opts = {}) {
|
|
21
|
+
const cache = new Map();
|
|
22
|
+
return (text, ctx) => {
|
|
23
|
+
let rules = cache.get(ctx.language);
|
|
24
|
+
if (!rules) {
|
|
25
|
+
rules = buildRules(opts, ctx.language);
|
|
26
|
+
cache.set(ctx.language, rules);
|
|
27
|
+
}
|
|
28
|
+
return parseWithRules(text, ctx, rules);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function parse(text, ctx, opts) {
|
|
32
|
+
return parseWithRules(text, ctx, buildRules(opts, ctx.language));
|
|
33
|
+
}
|
|
34
|
+
function parseWithRules(text, ctx, rules) {
|
|
5
35
|
const tokens = tokenize(text);
|
|
6
36
|
const raw = [];
|
|
7
37
|
let i = 0;
|
|
8
38
|
while (i < tokens.length) {
|
|
9
39
|
let best;
|
|
10
|
-
for (const rule of
|
|
40
|
+
for (const rule of rules) {
|
|
11
41
|
const m = rule(tokens, i, ctx);
|
|
12
42
|
if (!m)
|
|
13
43
|
continue;
|
|
@@ -24,9 +54,10 @@ export function parse(text, ctx) {
|
|
|
24
54
|
}
|
|
25
55
|
}
|
|
26
56
|
// Merge to fixpoint: [date, duration, time] needs two passes.
|
|
27
|
-
|
|
57
|
+
const connectors = languageDef(ctx.language).connectors;
|
|
58
|
+
let merged = mergeAdjacent(raw, tokens, connectors);
|
|
28
59
|
for (let pass = 0; pass < 2; pass += 1) {
|
|
29
|
-
const again = mergeAdjacent(merged, tokens);
|
|
60
|
+
const again = mergeAdjacent(merged, tokens, connectors);
|
|
30
61
|
if (again.length === merged.length)
|
|
31
62
|
break;
|
|
32
63
|
merged = again;
|
|
@@ -52,7 +83,7 @@ export function parse(text, ctx) {
|
|
|
52
83
|
* separated by a connector token) into intersect(date, time) — "yesterday at
|
|
53
84
|
* 9pm", "next Tuesday morning", "May 29 at noon".
|
|
54
85
|
*/
|
|
55
|
-
function mergeAdjacent(matches, tokens) {
|
|
86
|
+
function mergeAdjacent(matches, tokens, connectors) {
|
|
56
87
|
const out = [];
|
|
57
88
|
let k = 0;
|
|
58
89
|
while (k < matches.length) {
|
|
@@ -88,7 +119,7 @@ function mergeAdjacent(matches, tokens) {
|
|
|
88
119
|
isWeekExpr(cur.expr) &&
|
|
89
120
|
next.expr.op === 'seek' &&
|
|
90
121
|
next.expr.target.kind === 'weekday' &&
|
|
91
|
-
isAdjacent(cur, next, tokens)) {
|
|
122
|
+
isAdjacent(cur, next, tokens, connectors)) {
|
|
92
123
|
out.push({
|
|
93
124
|
expr: { op: 'seek', base: cur.expr, dir: 'next', target: next.expr.target, n: 1 },
|
|
94
125
|
consumed: next.tokenEnd - cur.tokenStart,
|
|
@@ -100,7 +131,7 @@ function mergeAdjacent(matches, tokens) {
|
|
|
100
131
|
k += 2;
|
|
101
132
|
continue;
|
|
102
133
|
}
|
|
103
|
-
if (next && canMerge(cur, next) && isAdjacent(cur, next, tokens)) {
|
|
134
|
+
if (next && canMerge(cur, next) && isAdjacent(cur, next, tokens, connectors)) {
|
|
104
135
|
const [datePart, timePart] = cur.role === 'time' ? [next, cur] : [cur, next];
|
|
105
136
|
let dateExpr = datePart.expr;
|
|
106
137
|
let timeExpr = inferMeridiem(dateExpr, timePart.expr);
|
|
@@ -220,11 +251,11 @@ const REDUNDANT_BETWEEN = new Set([
|
|
|
220
251
|
'mon', 'tue', 'tues', 'wed', 'weds', 'thu', 'thur', 'thurs', 'fri', 'sat', 'sun',
|
|
221
252
|
'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
|
|
222
253
|
]);
|
|
223
|
-
function isAdjacent(a, b, tokens) {
|
|
254
|
+
function isAdjacent(a, b, tokens, connectors) {
|
|
224
255
|
const between = tokens.slice(a.tokenEnd, b.tokenStart);
|
|
225
256
|
if (between.length > 2)
|
|
226
257
|
return false;
|
|
227
258
|
// A parenthesized weekday between date and time ("May/22 (Tue) 11:30") is
|
|
228
259
|
// redundant with the date and doesn't block the merge.
|
|
229
|
-
return between.every((t) => t.type === 'word' && (
|
|
260
|
+
return between.every((t) => t.type === 'word' && (connectors.includes(t.value) || REDUNDANT_BETWEEN.has(t.value)));
|
|
230
261
|
}
|