Initial commit
This commit is contained in:
commit
574ccc87bf
204
async.ts
Normal file
204
async.ts
Normal 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
60
bytes.ts
Normal 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
9
deno.json
Normal 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
21
deno.lock
generated
Normal 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
132
events.ts
Normal 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
32
func.ts
Normal 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
113
jit.ts
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user