From 574ccc87bfb857f1325021b697d169f7084aa0cf Mon Sep 17 00:00:00 2001 From: luaneko Date: Tue, 7 Jan 2025 02:58:12 +1100 Subject: [PATCH] Initial commit --- async.ts | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ bytes.ts | 60 ++++++++++++++++ deno.json | 9 +++ deno.lock | 21 ++++++ events.ts | 132 +++++++++++++++++++++++++++++++++++ func.ts | 32 +++++++++ jit.ts | 113 ++++++++++++++++++++++++++++++ mod.ts | 0 8 files changed, 571 insertions(+) create mode 100644 async.ts create mode 100644 bytes.ts create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 events.ts create mode 100644 func.ts create mode 100644 jit.ts create mode 100644 mod.ts diff --git a/async.ts b/async.ts new file mode 100644 index 0000000..ac222b9 --- /dev/null +++ b/async.ts @@ -0,0 +1,204 @@ +function noop() {} + +export interface Deferred extends Promise { + resolve(value: T | PromiseLike): void; + reject(reason?: unknown): void; +} + +export function deferred() { + const { promise, resolve, reject } = Promise.withResolvers(); + const p = promise as Deferred; + return (p.resolve = resolve), (p.reject = reject), p; +} + +export function notifier() { + let p: Deferred | null = null; + + return { + listen(): Promise { + return (p ??= deferred()); + }, + + notify(value: T) { + p &&= (p.resolve(value), null); + }, + }; +} + +export interface Channel { + send: Sender; + recv: Receiver; +} + +export interface Sender { + (value: T): void; + close(reason?: unknown): void; +} + +export interface Receiver { + (): Promise | T | null; + try(): T | null; + close(reason?: unknown): void; +} + +export function channel(): Channel { + const q = new Map(); + let head = 0; + let tail = 0; + let open = true; + let err: unknown = null; + + type Entry = + | { has: true; value: T } + | { has: false; value: Deferred }; + + function send(value: T) { + if (open) { + const i = head++; + let n = q.get(i); + if (!n) q.set(i, (n = { has: true, value })); + else if (!n.has) q.delete(i), n.value.resolve(value); + else n.value = value; + } else { + if (err !== null) throw err; + else return; + } + } + + function recv() { + if (open) { + const i = tail++; + let n = q.get(i); + if (!n) return q.set(i, (n = { has: false, value: deferred() })), n.value; + else if (!n.has) return n.value; + else return q.delete(i), n.value; + } else { + if (err !== null) throw err; + else return null; + } + } + + function try_recv() { + if (open) { + const n = q.get(tail); + if (n?.has) return q.delete(tail++), n.value; + else return null; + } else { + return null; + } + } + + recv.try = try_recv; + send.close = recv.close = function close(reason?: unknown) { + if (!open) return; + else (open = false), (err = reason ?? null); + for (const p of q.values()) { + if (p.has) continue; + else if (err !== null) p.value.reject(err); + else p.value.resolve(null); + } + q.clear(); + }; + + return { send, recv }; +} + +channel.sender = function sender( + f: (recv: Receiver) => void | PromiseLike +): Sender { + const { send, recv } = channel(); + Promise.resolve(f(recv)).then(noop).then(recv.close, recv.close); + return send; +}; + +channel.receiver = function receiver( + f: (send: Sender) => void | PromiseLike +): Receiver { + const { send, recv } = channel(); + Promise.resolve(f(send)).then(noop).then(send.close, send.close); + return recv; +}; + +export function semaphore(count = 1) { + const { send: signal, recv: wait } = channel(); + let n = count; + + async function acquire() { + if (--n < 0) await wait(); + return release; + } + + function release() { + if (n++ < 0) signal(); + } + + function reset(new_count = count) { + n = count = new_count; + } + + acquire.release = release; + acquire.reset = reset; + release[Symbol.dispose] = release; + release satisfies Disposable; + + return acquire; +} + +export interface PoolToken extends Iterable { + readonly value: T; + release(): void; +} + +export function pool(size: number, f: () => T | PromiseLike) { + const lock = semaphore(size); + const all = new Set(); + const free: T[] = []; + const Token = class implements PoolToken { + #value; + #open = true; + + get value() { + if (this.#open) return this.#value; + else throw new TypeError(`borrowed value is already released`); + } + + constructor(value: T) { + this.#value = value; + } + + *[Symbol.iterator]() { + yield this.value; + } + + release() { + this.#open &&= + (lock.release(), all.has(this.#value) && free.push(this.#value), false); + } + + [Symbol.dispose]() { + this.release(); + } + }; + + async function acquire(): Promise> { + await lock(); + try { + let value; + if (free.length !== 0) value = free.pop() as T; + else value = await f(); + return all.add(value), new Token(value); + } catch (e) { + throw (lock.release(), e); + } + } + + function forget(value: T) { + if (all.delete(value)) { + const idx = free.indexOf(value); + if (idx !== -1) free.splice(idx, 1); + } + } + + acquire.forget = forget; + return acquire; +} diff --git a/bytes.ts b/bytes.ts new file mode 100644 index 0000000..685c2db --- /dev/null +++ b/bytes.ts @@ -0,0 +1,60 @@ +import { + decodeBase64, + decodeHex, + encodeBase64, + encodeHex, +} from "@std/encoding"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +export type BinaryLike = string | Uint8Array; + +export function from_utf8(s: BinaryLike): string { + return typeof s === "string" ? s : decoder.decode(s); +} + +export function to_utf8(s: BinaryLike): Uint8Array { + return typeof s === "string" ? encoder.encode(s) : s; +} + +export function encode_utf8(s: BinaryLike, buf: Uint8Array): number { + if (typeof s === "string") return encoder.encodeInto(s, buf).written; + else return buf.set(s), s.length; +} + +export function from_hex(s: BinaryLike): Uint8Array { + return decodeHex(from_utf8(s)); +} + +export function to_hex(b: BinaryLike): string { + return encodeHex(to_utf8(b)); +} + +export function from_base64(s: BinaryLike): Uint8Array { + return decodeBase64(from_utf8(s)); +} + +export function to_base64(b: BinaryLike): string { + return encodeBase64(to_utf8(b)); +} + +export { equals as buf_eq, concat as buf_concat } from "@std/bytes"; + +export function buf_concat_fast(a: Uint8Array, b: Uint8Array) { + const m = a.length; + const c = new Uint8Array(m + b.length); + return c.set(a, 0), c.set(b, m), c; +} + +export function buf_resize(buf: Uint8Array, n: number) { + const res = new Uint8Array(n); + return res.set(buf.subarray(0, n)), res; +} + +export function buf_xor(a: Uint8Array, b: Uint8Array) { + const n = Math.max(a.length, b.length); + const c = new Uint8Array(n); + for (let i = 0; i < n; i++) c[i] = a[i] ^ b[i]; + return c; +} diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..d378fb7 --- /dev/null +++ b/deno.json @@ -0,0 +1,9 @@ +{ + "name": "@luaneko/lstd", + "version": "0.1.0", + "exports": "./mod.ts", + "imports": { + "@std/bytes": "jsr:@std/bytes@^1.0.4", + "@std/encoding": "jsr:@std/encoding@^1.0.6" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..f156b53 --- /dev/null +++ b/deno.lock @@ -0,0 +1,21 @@ +{ + "version": "4", + "specifiers": { + "jsr:@std/bytes@^1.0.4": "1.0.4", + "jsr:@std/encoding@^1.0.6": "1.0.6" + }, + "jsr": { + "@std/bytes@1.0.4": { + "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" + }, + "@std/encoding@1.0.6": { + "integrity": "ca87122c196e8831737d9547acf001766618e78cd8c33920776c7f5885546069" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/bytes@^1.0.4", + "jsr:@std/encoding@^1.0.6" + ] + } +} diff --git a/events.ts b/events.ts new file mode 100644 index 0000000..e0413dc --- /dev/null +++ b/events.ts @@ -0,0 +1,132 @@ +// deno-lint-ignore-file no-explicit-any +import { jit } from "./jit.ts"; + +export class Emitter void> { + #ls: { f: F; once: boolean }[] = []; + #argc = 0; + + get argc() { + return this.#argc; + } + + get size() { + return this.#ls.length; + } + + constructor() { + this.emit = this.#emit_fast; + } + + on(f: F, once: boolean) { + this.#ls.push({ f, once }); + this.#argc = Math.max(this.#argc, f.length); + + if (once) this.emit = this.#emit_slow; + else this.#emit_fast = this.#compile(); + if (this.emit !== this.#emit_slow) this.emit = this.#emit_fast; + return this; + } + + off(f: F) { + const ls = this.#ls; + let once = false; + + for (let i = ls.length; i-- !== 0; ) { + if (ls[i].f === f) { + (once = ls[i].once), ls.splice(i, 1); + break; + } + } + + this.#argc = ls.length ? Math.max(...ls.map(({ f }) => f.length)) : 0; + if (!once) this.#emit_fast = this.#compile(); + if (this.emit !== this.#emit_slow) this.emit = this.#emit_fast; + return this; + } + + // deno-lint-ignore no-unused-vars + emit(...args: Parameters) { + return false; + } + + listeners() { + return this.#ls.map(({ f }) => f); + } + + #emit_fast = this.#compile(); + #emit_slow(...args: Parameters) { + const ls = this.#ls; + (this.#ls = ls.filter(({ once }) => !once)), (this.emit = this.#emit_fast); + + for (let i = 0, n = ls.length; i < n; i++) + Reflect.apply(ls[i].f, undefined, args); + + return ls.length !== 0; + } + + #compile(): typeof this.emit { + const ls = this.#ls.filter(({ once }) => !once); + const ps = [...Array(this.#argc).keys()].map((i) => jit.raw`a${i}`); + + return jit.compiled`function emit(${jit.fragment(", ", ...ps)}) { + ${jit.map(" ", ls, ({ f }) => { + return jit`${f}(${jit.fragment(", ", ...ps.slice(0, f.length))});`; + })} + return ${jit.literal(ls.length !== 0)}; + }`; + } +} + +export type EventMap = Record void>; + +export class TypedEmitter { + readonly #events: { [K in keyof T]?: Emitter } = Object.create(null); + + on(name: K, f: T[K]) { + return (this.#events[name] ??= new Emitter()).on(f, false), this; + } + + once(name: K, f: T[K]) { + return (this.#events[name] ??= new Emitter()).on(f, true), this; + } + + off(name: K, f: T[K]) { + const events = this.#events; + if (events[name]?.off(f).size === 0) delete events[name]; + return this; + } + + emit(name: K, ...args: Parameters) { + const event = this.#events[name]; + if (event) return event.emit.apply(event, args); + else return false; + } + + listeners(name: K) { + return this.#events[name]?.listeners() ?? []; + } + + static { + this.prototype.emit = function emit( + this: TypedEmitter, + name: string, + a: any, + b: any, + c: any, + d: any, + e: any + ) { + const event = this.#events[name]; + if (!event) return false; + + const { argc } = event; + if (argc < 6) { + return event.emit(a, b, c, d, e); + } else { + const args = Array(argc); + for (let i = 0; i < argc; i++) args[i] = arguments[i + 1]; + return event.emit.apply(undefined, args); + } + } as any; + } +} diff --git a/func.ts b/func.ts new file mode 100644 index 0000000..9dbb747 --- /dev/null +++ b/func.ts @@ -0,0 +1,32 @@ +// deno-lint-ignore-file no-explicit-any +export class Callable { + static { + Object.setPrototypeOf(this.prototype, Function.prototype); + } + + get name() { + return this.constructor.name; + } + + constructor(f: (...args: any) => any) { + let self: any; + + if (typeof f.prototype !== "undefined") { + self = function call(...args: unknown[]) { + return typeof new.target !== "undefined" + ? Reflect.construct(f, args, new.target) + : f(...args); + }; + + self.prototype = f.prototype; + } else { + self = (...args: unknown[]) => f(...args); + } + + f = f.bind(self); + + delete self.name; + delete self.length; + return Object.setPrototypeOf(self, new.target.prototype); + } +} diff --git a/jit.ts b/jit.ts new file mode 100644 index 0000000..394bc27 --- /dev/null +++ b/jit.ts @@ -0,0 +1,113 @@ +export const jit_format = Symbol.for(`re.lua.lstd.jit_format`); + +export interface JitFragment { + [jit_format](c: JitCompiler): void; +} + +export function is_jit(x: unknown): x is JitFragment { + return typeof x === "object" && x !== null && jit_format in x; +} + +export class JitCompiler { + #body = ""; + #args = new Map(); + + write(s: string | JitFragment) { + if (is_jit(s)) s[jit_format](this); + else this.#body += s; + } + + enclose(x: unknown) { + let name = this.#args.get(x); + if (!name) this.#args.set(x, (name = `__x${this.#args.size}`)); + this.write(name); + } + + format(x: unknown) { + is_jit(x) ? this.write(x) : this.enclose(x); + } + + compile() { + return new Function( + ...this.#args.values(), + `"use strict"; return (${this.#body});` + )(...this.#args.keys()); + } +} + +export function jit( + { raw: s }: TemplateStringsArray, + ...xs: unknown[] +): JitFragment { + return { + [jit_format](c) { + for (let i = 0, n = s.length; i < n; i++) { + if (i !== 0) c.format(xs[i - 1]); + c.write(s[i]); + } + }, + }; +} + +jit.compiled = compiled; +jit.raw = raw; +jit.fragment = fragment; +jit.literal = literal; +jit.map = map; +jit.if = condition; + +export function compiled( + s: TemplateStringsArray, + ...xs: unknown[] +): T { + const c = new JitCompiler(); + return c.write(jit(s, ...xs)), c.compile(); +} + +export function raw(s: TemplateStringsArray, ...xs: unknown[]): JitFragment; +export function raw(s: string): JitFragment; +export function raw( + s: TemplateStringsArray | string, + ...xs: unknown[] +): JitFragment { + s = typeof s === "string" ? s : String.raw(s, ...xs); + return { + [jit_format](c) { + c.write(s); + }, + }; +} + +export function fragment( + sep: string | JitFragment, + ...xs: unknown[] +): JitFragment { + return { + [jit_format](c) { + for (let i = 0, n = xs.length; i < n; i++) { + if (i !== 0) c.write(sep); + c.format(xs[i]); + } + }, + }; +} + +export function literal(x: unknown) { + return raw(typeof x === "undefined" ? "undefined" : JSON.stringify(x)); +} + +export function map( + sep: string | JitFragment, + xs: Iterable, + f: (value: T, index: number) => unknown +): JitFragment { + return fragment(sep, ...Iterator.from(xs).map(f)); +} + +export function condition( + test: unknown, + consequent: JitFragment, + alternate: JitFragment = jit`` +): JitFragment { + return test ? consequent : alternate; +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..e69de29