import { type StringDictionary, type JSNode, type IDisposable, type JSExpression, type JSFunction, specTypes, isNode, toDisposable, Disposable, } from '@alilc/lowcode-shared'; import { type ICodeScope, CodeScope } from './codeScope'; import { mapValue } from './value'; import { evaluate } from './evaluate'; export interface CodeRuntimeOptions { initScopeValue?: Partial; parentScope?: ICodeScope; evalCodeFunction?: EvalCodeFunction; } export interface ICodeRuntime extends IDisposable { getScope(): ICodeScope; run(code: string, scope?: ICodeScope): R | undefined; resolve(value: StringDictionary): any; onResolve(handler: NodeResolverHandler): IDisposable; createChild( options: Omit, 'parentScope'>, ): ICodeRuntime; } export type NodeResolverHandler = (node: JSNode) => JSNode | false | undefined; export type EvalCodeFunction = (code: string, scope: any) => any; export class CodeRuntime extends Disposable implements ICodeRuntime { private _codeScope: ICodeScope; private _evalCodeFunction: EvalCodeFunction = evaluate; private _resolveHandlers: NodeResolverHandler[] = []; constructor(options: CodeRuntimeOptions = {}) { super(); if (options.evalCodeFunction) this._evalCodeFunction = options.evalCodeFunction; this._codeScope = this._addDispose( options.parentScope ? options.parentScope.createChild(options.initScopeValue ?? {}) : new CodeScope(options.initScopeValue ?? {}), ); } getScope() { return this._codeScope; } run(code: string): R | undefined { this._throwIfDisposed(`this code runtime has been disposed`); if (!code) return undefined; try { const result = this._evalCodeFunction(code, this._codeScope.value); return result as R; } catch (err) { // todo replace logger console.error('eval error', code, this._codeScope.value, err); return undefined; } } resolve(data: StringDictionary): any { if (this._resolveHandlers.length > 0) { data = mapValue(data, isNode, (node: JSNode) => { let newNode: JSNode | false | undefined = node; for (const handler of this._resolveHandlers) { newNode = handler(newNode as JSNode); if (newNode === false || typeof newNode === 'undefined') { break; } } return newNode; }); } return mapValue( data, (data) => { return specTypes.isJSExpression(data) || specTypes.isJSFunction(data); }, (node: JSExpression | JSFunction) => { return this.resolveExprOrFunction(node); }, ); } private resolveExprOrFunction(node: JSExpression | JSFunction) { const v = this.run(node.value) as any; if (typeof v === 'undefined' && node.mock) { return this.resolve(node.mock); } return v; } /** * 顺序执行 handler */ onResolve(handler: NodeResolverHandler): IDisposable { this._resolveHandlers.push(handler); return this._addDispose( toDisposable(() => { this._resolveHandlers = this._resolveHandlers.filter((h) => h !== handler); }), ); } createChild( options?: Omit, 'parentScope'>, ): ICodeRuntime { return this._addDispose( new CodeRuntime({ initScopeValue: options?.initScopeValue, parentScope: this._codeScope, evalCodeFunction: options?.evalCodeFunction ?? this._evalCodeFunction, }), ); } }