abi.js
Version:
[![typescript-icon]][typescript-link] [![license-icon]][license-link] [![status-icon]][status-link] [![ci-icon]][ci-link] [![twitter-icon]][twitter-link]
207 lines (175 loc) • 4.81 kB
text/typescript
import 'reflect-metadata';
import { parse_params } from './parser';
export function reflect<T extends Function | object>(
value: T,
objectThis?: object,
): ReflectionObject | ReflectionFunction {
return typeof value === 'object'
? new ReflectionObject(value, objectThis)
: new ReflectionFunction(value, objectThis);
}
export class ReflectionError extends Error {}
export abstract class Reflection<T> {
constructor(readonly value: T) {}
toString(): string {
return String(this.value);
}
}
export class ReflectionObject extends Reflection<object> {
constructor(
value: object,
protected objectThis?: object,
) {
super(value);
}
override toString(): string {
return String(this.value.constructor);
}
}
export class ReflectionFunction extends Reflection<Function> {
protected is_arrow = false;
protected name: string;
readonly parameters: ReflectionParameter<any>[] = [];
constructor(
value: Function,
protected objectThis?: object,
) {
super(value);
const str_val = value.toString();
const arrow_re = /^\(([^\(\)]*?)\)\s*\=\>\s*(.+)$/gms;
const named_re =
/^(?:function\s+)?([a-zA-Z$_]+[a-zA-Z0-9$_]*)\s*\(([^\(\)]*?)\)\s*\{(.*)\}$/gms;
const arrow_res = arrow_re.exec(str_val);
const named_res = named_re.exec(str_val);
if (arrow_res) {
this.is_arrow = true;
this.name = '';
this.#parseParameters(arrow_res[1]);
this.#parseReturn(arrow_res[2]);
} else if (named_res) {
this.is_arrow = false;
this.name = named_res[1] || 'anonymous';
this.#parseParameters(named_res[2]);
this.#parseReturn(named_res[3]);
} else {
throw new ReflectionError(`${value} is not a function`);
}
}
#parseParameters(str_params: string) {
const default_params = parse_params(str_params);
const entries = Object.entries(default_params);
if (
entries.length === 1 &&
entries[0][0] === '0' &&
entries[0][1] === undefined
) {
return;
}
const i = 0;
for (const param of entries) {
this.parameters.push(
new ReflectionParameter(this, param[0], i, param[1]),
);
}
}
getParameter<T>(name: string): ReflectionParameter<T> | undefined {
for (const param of this.parameters) {
if (param.name === name) {
return param;
}
}
return undefined;
}
hasParameter(name: string): boolean {
for (const param of this.parameters) {
if (param.name === name) {
return true;
}
}
return false;
}
#parseReturn(_str_ret: string) {
// TODO: throw new ReflectionError("Not implemented!");
}
}
export class ReflectionParameter<T> extends Reflection<T> {
constructor(
readonly func: ReflectionFunction,
readonly name: string,
readonly index: number,
value: T,
) {
super(value);
}
getFunction(): ReflectionFunction {
return this.func;
}
hasDefaultValue(): boolean {
return this.value !== undefined;
}
getDefaultValue(): ReflectionParameterValue<T> {
return new ReflectionParameterValue<T>(this, this.value);
}
}
export class ReflectionValue<T> extends Reflection<T> {
get type(): string {
return typeof this.value;
}
}
export class ReflectionParameterValue<T> extends ReflectionValue<T> {
constructor(
protected param: ReflectionParameter<T>,
value: T,
) {
super(value);
}
getParameter(): ReflectionParameter<T> {
return this.param;
}
}
export class ReflectionReturnValue<T> extends ReflectionValue<T> {
constructor(
protected func: ReflectionFunction,
value: T,
) {
super(value);
}
getFunction(): ReflectionFunction {
return this.func;
}
}
export type Context = Record<string, any>;
export class Container {
constructor(protected context: Context) {}
call<T>(callback: Function, context: Context = {}): T {
return callback(...this.getArgs(callback, context));
}
getArgs(callback: Function, context: Context = {}): any[] {
const reflection = reflect(callback);
const args: any[] = [];
for (const param of (reflection as ReflectionFunction).parameters) {
args.push(this.get(param.name, param.value, context));
}
return args;
}
get<T extends unknown | undefined>(
id: string,
defaultValue?: T,
context: Context = {},
): T {
context = this.mergeContext(context);
return this.make(context[id] || defaultValue, context);
}
make<T>(value: T, context: Context = {}): T {
return typeof value === 'function' ? this.call(value, context) : value;
}
mergeContext(context: Context): Context {
return {
...context,
...this.context,
};
}
}
export default function (context: Context) {
return new Container(context);
}