UNPKG

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