This commit is contained in:
JOLIMAITRE Matthieu 2024-06-15 00:22:28 +02:00
commit 6694c7eb9a
10 changed files with 328 additions and 0 deletions

10
src/lib.ts Normal file
View file

@ -0,0 +1,10 @@
// types
export type { Arr, ClassOf, Constructible, Function, InstanceOf, KeyOfType, Tail } from "./lib/types.ts";
// functions
export { all, enumerate, filter, filter_map, it, Iter, map, zip } from "./lib/iter.ts";
export { next, range, split_promise, wait } from "./lib/utils.ts";
// Structures
export { Chain } from "./lib/Chain.ts";
export { Channel } from "./lib/Channel.ts";

113
src/lib/Chain.ts Normal file
View file

@ -0,0 +1,113 @@
import { Arr, Function, KeyOfType, Tail } from "./types.ts";
import { map } from "./iter.ts";
export class Chain<T extends Arr> {
run;
constructor(run: () => T) {
this.run = run;
}
static from<T>(value: T) {
return new Chain(() => [value] as [T]);
}
to<A extends Arr, O>(map: Function<[...T, ...A], O>, ...args: A) {
const this_run = this.run;
return new Chain(() => [map(...this_run(), ...args)] as [O]);
}
front<I extends Arr>(...values: I) {
const this_run = this.run;
return new Chain(() => [...values, ...this_run()] as [...I, ...T]);
}
back<I extends Arr>(...values: I) {
const this_run = this.run;
return new Chain(() => [...this_run(), ...values] as [...T, ...I]);
}
nth<Index extends number>(index: Index) {
const this_run = this.run;
return new Chain(() => [this_run()[index]] as T[Index]);
}
call<
A extends Arr,
Arg extends [...Tail<T>, ...A],
M extends KeyOfType<T[0], Function<Arg>>,
O extends ReturnType<T[0][M]>,
>(method: M, ...args: A) {
const this_run = this.run;
return new Chain(() => {
const [instance, ...rest] = this_run();
return [instance[method](...rest, ...args)] as [O];
});
}
call_ignore<
A extends Arr,
Arg extends [...Tail<T>, ...A],
M extends KeyOfType<T[0], Function<Arg>>,
>(method: M, ...args: A) {
const this_run = this.run;
return new Chain(() => {
const res1: T = this_run();
const [instance, ...rest] = res1;
instance[method](...rest, ...args);
return res1;
});
}
call_keep<
A extends Arr,
Arg extends [...Tail<T>, ...A],
M extends KeyOfType<T[0], Function<Arg>>,
O extends ReturnType<T[0][M]>,
>(method: M, ...args: A) {
const this_run = this.run;
return new Chain(() => {
const res1 = this_run();
const [instance, ...rest] = res1;
const res2: O = instance[method](...rest, ...args);
return [...res1, res2] as [...T, O];
});
}
construct<
A extends Arr,
C extends new (...args: [...T, ...A]) => unknown,
O extends InstanceType<C>,
>(class_: C, ...args: A) {
const this_run = this.run;
return new Chain(() => [new class_(...this_run(), ...args)] as [O]);
}
}
Deno.test("example", async () => {
const { assertEquals } = await import("https://deno.land/std@0.224.0/assert/mod.ts");
function times_2(num: number) {
return num * 2;
}
// type: number
const [_o1] = Chain.from(3).to(times_2).run();
// type: number
const [_o2] = Chain.from(3).front(2, 5).to(Math.max).run();
function parse_and_add(text: string, num: number) {
return parseInt(text) + num;
}
// type: string
const [_o3] = Chain.from(3).front("5").to(parse_and_add).call("toString").run();
// type: Set<number>
const [_o4] = Chain.from([1, 2, 3])
.call_keep("push", 7)
.construct(Set<number>)
.call_ignore("add", 8)
.to(map, (item: number) => item.toString())
.run();
assertEquals([..._o4], ["1", "2", "3", "7", "8"]);
});

23
src/lib/Channel.ts Normal file
View file

@ -0,0 +1,23 @@
import { split_promise } from "./utils.ts";
export class Channel<T> {
queue_receiving = [] as ((item: T) => void)[];
queue_sending = [] as (() => T)[];
async send(item: T) {
const [first] = this.queue_receiving.splice(0, 1);
if (first !== undefined) return first(item);
const [promise, resolve] = split_promise();
const fn = () => ([resolve(), item] as const)[1];
this.queue_sending.push(fn);
await promise;
}
async receive() {
const [first] = this.queue_sending.splice(0, 1);
if (first !== undefined) return first();
const [promise, resolve] = split_promise<T>();
this.queue_receiving.push(resolve);
return await promise;
}
}

97
src/lib/iter.ts Normal file
View file

@ -0,0 +1,97 @@
import { next, range } from "./utils.ts";
export function it<T>(iter: Iterable<T>) {
return new Iter(iter);
}
export function* map<I, O>(iter: Iterable<I>, mapping: (item: I) => O): Generator<O, void, void> {
for (const item of iter) yield mapping(item);
}
export function* filter<I>(iter: Iterable<I>, predicate: (item: I) => boolean): Generator<I, void, void> {
for (const item of iter) if (predicate(item)) yield item;
}
export function filter_map<I, O>(
iter: Iterable<I>,
mapping: (item: I) => O,
): Generator<Exclude<O, null | undefined>, void, void> {
const filtered = filter(map(iter, mapping), (item) => item !== null && item !== undefined);
return filtered as Generator<Exclude<O, null | undefined>, void, void>;
}
export function all<I>(iter: Iterable<I>, predicate: (item: I) => boolean) {
for (const item of iter) if (!predicate(item)) return false;
return true;
}
export function* enumerate<T>(iterator: Iterable<T>): Generator<[number, T], void, void> {
let index = 0;
for (const item of iterator) yield [index++, item];
}
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 class Iter<T> {
iter;
[Symbol.iterator] = () => this.iter[Symbol.iterator]();
constructor(iter: Iterable<T>) {
this.iter = iter;
}
map<O>(mapping: (item: T) => O) {
return new Iter(map(this, mapping));
}
filter(predicate: (item: T) => boolean) {
return new Iter(filter(this, predicate));
}
filter_map<O>(mapping: (item: T) => O) {
return new Iter(filter_map(this, mapping));
}
enumerate() {
return new Iter(enumerate(this));
}
zip<U>(other: Iterable<U>) {
return new Iter(zip(this, other));
}
array() {
// deno-lint-ignore no-explicit-any
const result: any = [...this];
result.it = () => it(result);
return result as T[] & { it: () => Iter<T> };
}
collect<O>(class_: new (it: Iterable<T>) => O): O {
return new class_(this);
}
all(predicate: (item: T) => boolean) {
return all(this, predicate);
}
}
Deno.test("iter", async () => {
const items = it(range(0, 10))
.map((e) => e * 3)
.enumerate()
.filter(([_, e]) => (e % 2) == 1)
.zip(range(0, 10))
.map(JSON.stringify)
.collect(Set<string>);
const { assertEquals } = await import("https://deno.land/std@0.224.0/assert/assert_equals.ts");
assertEquals(items, new Set(["[[1,3],0]", "[[3,9],1]", "[[5,15],2]", "[[7,21],3]", "[[9,27],4]"]));
});

12
src/lib/types.ts Normal file
View file

@ -0,0 +1,12 @@
// deno-lint-ignore no-explicit-any
export type Arr<T = any> = T[];
// deno-lint-ignore no-explicit-any
export type Function<I extends Arr, O = any> = (...input: I) => O;
// deno-lint-ignore no-explicit-any
export type Tail<A extends Arr> = A extends [any, ...infer T] ? T : never;
// deno-lint-ignore no-explicit-any
export type KeyOfType<T, V> = keyof { [P in keyof T as T[P] extends V ? P : never]: any };
export type InstanceOf<C = unknown> = { constructor: { prototype: C } };
export type Constructible<I, Args extends Arr> = new (...args: Args) => I;
export type ClassOf<I = unknown> = Constructible<I, unknown[]>;

23
src/lib/utils.ts Normal file
View file

@ -0,0 +1,23 @@
import { Function } from "./types.ts";
export function split_promise<T = void>() {
let resolver_or_null = null as null | Function<[T], void>;
const promise = new Promise<T>((res) => resolver_or_null = res);
if (resolver_or_null === null) throw new Error("Unreachable.");
const resolver = resolver_or_null;
return [promise, resolver] as const;
}
export function wait(ms: number) {
return new Promise((res) => setTimeout(res, ms));
}
export function next<T>(iterator: Iterator<T>) {
const result = iterator.next().value;
if (result === undefined) return null;
else return result as T;
}
export function* range(from: number, to: number) {
while (from < to) yield from++;
}