From 6694c7eb9a849f7753709cc73af2e127f8cb2876 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 15 Jun 2024 00:22:28 +0200 Subject: [PATCH] init --- README.md | 5 ++ deno.json | 6 +++ deno.lock | 38 +++++++++++++++ mod.ts | 1 + src/lib.ts | 10 ++++ src/lib/Chain.ts | 113 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/Channel.ts | 23 +++++++++ src/lib/iter.ts | 97 ++++++++++++++++++++++++++++++++++++++ src/lib/types.ts | 12 +++++ src/lib/utils.ts | 23 +++++++++ 10 files changed, 328 insertions(+) create mode 100644 README.md create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 mod.ts create mode 100644 src/lib.ts create mode 100644 src/lib/Chain.ts create mode 100644 src/lib/Channel.ts create mode 100644 src/lib/iter.ts create mode 100644 src/lib/types.ts create mode 100644 src/lib/utils.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ab48ac --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Barnulf Ts + +A collection of utility functions and classes extending base JavaScript. + +Intended for the Deno runtime. diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..dd71398 --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "fmt": { + "lineWidth": 120, + "useTabs": true + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..83fe0e7 --- /dev/null +++ b/deno.lock @@ -0,0 +1,38 @@ +{ + "version": "3", + "remote": { + "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", + "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", + "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", + "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", + "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", + "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", + "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", + "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", + "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", + "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", + "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", + "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", + "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", + "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", + "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", + "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", + "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", + "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", + "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", + "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", + "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", + "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", + "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", + "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", + "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e" + } +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..8f7c8a1 --- /dev/null +++ b/mod.ts @@ -0,0 +1 @@ +export * from "./src/lib.ts"; diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..b74b6f1 --- /dev/null +++ b/src/lib.ts @@ -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"; diff --git a/src/lib/Chain.ts b/src/lib/Chain.ts new file mode 100644 index 0000000..5f9d49b --- /dev/null +++ b/src/lib/Chain.ts @@ -0,0 +1,113 @@ +import { Arr, Function, KeyOfType, Tail } from "./types.ts"; +import { map } from "./iter.ts"; + +export class Chain { + run; + + constructor(run: () => T) { + this.run = run; + } + + static from(value: T) { + return new Chain(() => [value] as [T]); + } + + to(map: Function<[...T, ...A], O>, ...args: A) { + const this_run = this.run; + return new Chain(() => [map(...this_run(), ...args)] as [O]); + } + + front(...values: I) { + const this_run = this.run; + return new Chain(() => [...values, ...this_run()] as [...I, ...T]); + } + + back(...values: I) { + const this_run = this.run; + return new Chain(() => [...this_run(), ...values] as [...T, ...I]); + } + + nth(index: Index) { + const this_run = this.run; + return new Chain(() => [this_run()[index]] as T[Index]); + } + + call< + A extends Arr, + Arg extends [...Tail, ...A], + M extends KeyOfType>, + O extends ReturnType, + >(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, ...A], + M extends KeyOfType>, + >(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, ...A], + M extends KeyOfType>, + O extends ReturnType, + >(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, + >(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 + const [_o4] = Chain.from([1, 2, 3]) + .call_keep("push", 7) + .construct(Set) + .call_ignore("add", 8) + .to(map, (item: number) => item.toString()) + .run(); + assertEquals([..._o4], ["1", "2", "3", "7", "8"]); +}); diff --git a/src/lib/Channel.ts b/src/lib/Channel.ts new file mode 100644 index 0000000..559834f --- /dev/null +++ b/src/lib/Channel.ts @@ -0,0 +1,23 @@ +import { split_promise } from "./utils.ts"; + +export class Channel { + 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(); + this.queue_receiving.push(resolve); + return await promise; + } +} diff --git a/src/lib/iter.ts b/src/lib/iter.ts new file mode 100644 index 0000000..f31350e --- /dev/null +++ b/src/lib/iter.ts @@ -0,0 +1,97 @@ +import { next, range } from "./utils.ts"; + +export function it(iter: Iterable) { + return new Iter(iter); +} + +export function* map(iter: Iterable, mapping: (item: I) => O): Generator { + for (const item of iter) yield mapping(item); +} + +export function* filter(iter: Iterable, predicate: (item: I) => boolean): Generator { + for (const item of iter) if (predicate(item)) yield item; +} + +export function filter_map( + iter: Iterable, + mapping: (item: I) => O, +): Generator, void, void> { + const filtered = filter(map(iter, mapping), (item) => item !== null && item !== undefined); + return filtered as Generator, void, void>; +} + +export function all(iter: Iterable, predicate: (item: I) => boolean) { + for (const item of iter) if (!predicate(item)) return false; + return true; +} + +export function* enumerate(iterator: Iterable): Generator<[number, T], void, void> { + let index = 0; + for (const item of iterator) yield [index++, item]; +} + +export function* zip(a: Iterable, b: Iterable): 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 { + iter; + [Symbol.iterator] = () => this.iter[Symbol.iterator](); + + constructor(iter: Iterable) { + this.iter = iter; + } + + map(mapping: (item: T) => O) { + return new Iter(map(this, mapping)); + } + + filter(predicate: (item: T) => boolean) { + return new Iter(filter(this, predicate)); + } + + filter_map(mapping: (item: T) => O) { + return new Iter(filter_map(this, mapping)); + } + + enumerate() { + return new Iter(enumerate(this)); + } + + zip(other: Iterable) { + 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 }; + } + + collect(class_: new (it: Iterable) => 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); + + 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]"])); +}); diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..f4c0e45 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,12 @@ +// deno-lint-ignore no-explicit-any +export type Arr = T[]; +// deno-lint-ignore no-explicit-any +export type Function = (...input: I) => O; +// deno-lint-ignore no-explicit-any +export type Tail = A extends [any, ...infer T] ? T : never; +// deno-lint-ignore no-explicit-any +export type KeyOfType = keyof { [P in keyof T as T[P] extends V ? P : never]: any }; + +export type InstanceOf = { constructor: { prototype: C } }; +export type Constructible = new (...args: Args) => I; +export type ClassOf = Constructible; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..8cc8631 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,23 @@ +import { Function } from "./types.ts"; + +export function split_promise() { + let resolver_or_null = null as null | Function<[T], void>; + const promise = new Promise((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(iterator: Iterator) { + 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++; +}