Initial commit

This commit is contained in:
luaneko 2025-01-07 02:58:12 +11:00
commit 574ccc87bf
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
8 changed files with 571 additions and 0 deletions

204
async.ts Normal file
View File

@ -0,0 +1,204 @@
function noop() {}
export interface Deferred<T = void> extends Promise<T> {
resolve(value: T | PromiseLike<T>): void;
reject(reason?: unknown): void;
}
export function deferred<T = void>() {
const { promise, resolve, reject } = Promise.withResolvers<T>();
const p = promise as Deferred<T>;
return (p.resolve = resolve), (p.reject = reject), p;
}
export function notifier<T = void>() {
let p: Deferred<T> | null = null;
return {
listen(): Promise<T> {
return (p ??= deferred());
},
notify(value: T) {
p &&= (p.resolve(value), null);
},
};
}
export interface Channel<T> {
send: Sender<T>;
recv: Receiver<T>;
}
export interface Sender<T> {
(value: T): void;
close(reason?: unknown): void;
}
export interface Receiver<T> {
(): Promise<T | null> | T | null;
try(): T | null;
close(reason?: unknown): void;
}
export function channel<T = void>(): Channel<T> {
const q = new Map<number, Entry>();
let head = 0;
let tail = 0;
let open = true;
let err: unknown = null;
type Entry =
| { has: true; value: T }
| { has: false; value: Deferred<T | null> };
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<T>(
f: (recv: Receiver<T>) => void | PromiseLike<void>
): Sender<T> {
const { send, recv } = channel<T>();
Promise.resolve(f(recv)).then(noop).then(recv.close, recv.close);
return send;
};
channel.receiver = function receiver<T>(
f: (send: Sender<T>) => void | PromiseLike<void>
): Receiver<T> {
const { send, recv } = channel<T>();
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<T> extends Iterable<T, void, void> {
readonly value: T;
release(): void;
}
export function pool<T>(size: number, f: () => T | PromiseLike<T>) {
const lock = semaphore(size);
const all = new Set<T>();
const free: T[] = [];
const Token = class implements PoolToken<T> {
#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<PoolToken<T>> {
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;
}

60
bytes.ts Normal file
View File

@ -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;
}

9
deno.json Normal file
View File

@ -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"
}
}

21
deno.lock generated Normal file
View File

@ -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"
]
}
}

132
events.ts Normal file
View File

@ -0,0 +1,132 @@
// deno-lint-ignore-file no-explicit-any
import { jit } from "./jit.ts";
export class Emitter<F extends (...args: any) => 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<F>) {
return false;
}
listeners() {
return this.#ls.map(({ f }) => f);
}
#emit_fast = this.#compile();
#emit_slow(...args: Parameters<F>) {
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<string | symbol, (...args: any) => void>;
export class TypedEmitter<T extends EventMap> {
readonly #events: { [K in keyof T]?: Emitter<T[K]> } = Object.create(null);
on<K extends keyof T>(name: K, f: T[K]) {
return (this.#events[name] ??= new Emitter()).on(f, false), this;
}
once<K extends keyof T>(name: K, f: T[K]) {
return (this.#events[name] ??= new Emitter()).on(f, true), this;
}
off<K extends keyof T>(name: K, f: T[K]) {
const events = this.#events;
if (events[name]?.off(f).size === 0) delete events[name];
return this;
}
emit<K extends keyof T>(name: K, ...args: Parameters<T[K]>) {
const event = this.#events[name];
if (event) return event.emit.apply(event, args);
else return false;
}
listeners<K extends keyof T>(name: K) {
return this.#events[name]?.listeners() ?? [];
}
static {
this.prototype.emit = function emit(
this: TypedEmitter<EventMap>,
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;
}
}

32
func.ts Normal file
View File

@ -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);
}
}

113
jit.ts Normal file
View File

@ -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<unknown, string>();
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<T = unknown>(
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<T>(
sep: string | JitFragment,
xs: Iterable<T>,
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;
}

0
mod.ts Normal file
View File