This commit is contained in:
JOLIMAITRE Matthieu 2024-08-07 10:17:45 +02:00
commit 8c18398ae4
6 changed files with 111 additions and 0 deletions

21
lib/extractor.ts Normal file
View file

@ -0,0 +1,21 @@
export class Extractor<O> {
private extract: (text: string) => O | null;
public constructor(extract: (text: string) => O | null) {
this.extract = extract;
}
public get(text: string) {
return this.extract(text);
}
public or<T>(other: Extractor<T>) {
const this_extract = this.extract;
return new Extractor(text => {
const r1 = this_extract(text);
if (r1 !== null) return r1;
else return other.extract(text);
});
}
}

28
lib/lib.ts Normal file
View file

@ -0,0 +1,28 @@
import { Rule } from "./rule.ts";
import { PairElts, StructOfArr } from "./types.ts";
import { Extractor } from "./extractor.ts"
export function extr<A extends string[]>(...args: A): Extractor<StructOfArr<PairElts<A>, string>> {
const seps = [] as string[];
const vars = [] as string[];
let pair = true;
for (const arg of args) {
if (pair) seps.push(arg);
else vars.push(arg);
pair = !pair;
}
const vars_ = vars as PairElts<A>;
const rule = new Rule(seps, vars_);
return new Extractor(text => rule.get(text))
}
Deno.test("types", async () => {
const { assertEquals } = await import("https://deno.land/std@0.224.0/assert/assert_equals.ts");
const vars = extr()
.or(extr("bonjour, ", 'animal', ", poilu"))
.or(extr("bonjour, ", 'arbre', ", feuillu"))
.get("bonjour, le chêne, feuillu");
assertEquals({ vars }, { vars: { "arbre": "le chêne" } })
})

34
lib/rule.ts Normal file
View file

@ -0,0 +1,34 @@
import { StructOfArr } from "./types.ts";
import { zip } from "./utils.ts";
export class Rule<V extends string[]> {
private separators: ({ kind: "word", word: string } | { kind: "end" })[];
private vars: V;
public constructor(separators: string[], vars: V) {
this.separators = separators.map(word => ({ kind: "word", word }));
this.vars = vars;
this.separators.push({ kind: "end" });
}
public get(text: string): StructOfArr<V, string> | null {
const first_sep = this.separators[0] ?? "";
if (first_sep.kind === "end") {
if (text.length > 0) return null;
else return {} as StructOfArr<V, string>;
}
if (!text.includes(first_sep.word)) return null;
let [val, rest] = text.split(first_sep.word, 2);
const result = {} as Record<string, string>;
for (const [key, sep] of zip(this.vars, this.separators.slice(1))) {
if (sep.kind === "end") {
result[key] = rest;
break;
}
if (!rest.includes(sep.word)) return null;
[val, rest] = rest.split(sep.word, 2);
result[key] = val;
}
return result as StructOfArr<V, string>;
}
}

13
lib/types.ts Normal file
View file

@ -0,0 +1,13 @@
// deno-lint-ignore no-explicit-any
export type PairElts<A extends any[]> =
A extends [] ? [] :
A extends [infer _F] ? [] :
A extends [infer _F, infer S, ...infer R] ? [S, ...PairElts<R>] :
never;
export type StructOfArr<A extends string[], V> =
// deno-lint-ignore ban-types
A extends [] ? {} :
A extends [infer K extends string, ...infer R extends string[]] ? { [key in K]: V } & StructOfArr<R, V> :
never;

14
lib/utils.ts Normal file
View file

@ -0,0 +1,14 @@
export function* zip<A, B>(a: Iterable<A>, b: Iterable<B>): Generator<[A, B], void, void> {
const iter_a = a[Symbol.iterator](), iter_b = b[Symbol.iterator]();
while (true) {
const next_a = next(iter_a), next_b = next(iter_b);
if (next_a === null || next_b === null) return;
yield [next_a, next_b];
}
}
export function next<T>(iterator: Iterator<T>) {
const result = iterator.next().value;
if (result === undefined) return null;
else return result as T;
}

1
mod.ts Normal file
View file

@ -0,0 +1 @@
import { extr } from "./lib/lib.ts"