nl2time 0.1.0 → 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.
@@ -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 declare function parse(text: string, ctx: TimeContext): ParseResult;
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;
@@ -1,13 +1,43 @@
1
- import { EN_RULES } from './en.js';
1
+ import { compilePack } from '../packs/index.js';
2
+ import { languageDef } from './languages.js';
2
3
  import { tokenize } from './tokenizer.js';
3
- const CONNECTORS = ['at', 'on', 'in', 'of', 'the', 'for', 'from', 'around', 'about', '-'];
4
- export function parse(text, ctx) {
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 EN_RULES) {
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
- let merged = mergeAdjacent(raw, tokens);
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' && (CONNECTORS.includes(t.value) || REDUNDANT_BETWEEN.has(t.value)));
260
+ return between.every((t) => t.type === 'word' && (connectors.includes(t.value) || REDUNDANT_BETWEEN.has(t.value)));
230
261
  }
@@ -0,0 +1,7 @@
1
+ import type { Rule } from './en.js';
2
+ export declare const JA_RULE_ENTRIES: readonly {
3
+ name: string;
4
+ rule: Rule;
5
+ }[];
6
+ /** Particles transparent between merged date & time parts ("明日の朝8時"). */
7
+ export declare const JA_CONNECTORS: string[];