UNPKG

@akala/core

Version:
270 lines 11.9 kB
import { ExpressionType } from '../expression-type.js'; import { BinaryExpression } from '../binary-expression.js'; import { UnaryExpression } from '../unary-expression.js'; import { TypedLambdaExpression } from '../lambda-expression.js'; import { MemberExpression } from '../member-expression.js'; import { CallExpression } from '../call-expression.js'; import { ApplySymbolExpression } from '../apply-symbol-expression.js'; import { NewExpression } from '../new-expression.js'; import { FormatExpression } from '../../parser.js'; import { TernaryExpression } from '../ternary-expression.js'; import { AssignmentExpression } from '../assignment-expression.js'; /** * Base class for implementing the Visitor pattern for expression tree processing. */ export class ExpressionVisitor { /** * Processes an assignment expression by visiting its left and right operands. * @template T - The expression's value type * @param {AssignmentExpression<T>} expression - The assignment expression to process. * @returns {AssignmentExpression<Expressions>} The processed assignment expression. */ visitAssign(expression) { const left = this.visit(expression.left); const right = this.visit(expression.right); return left !== expression.left || right !== expression.right ? new AssignmentExpression(left, expression.operator, right) : expression; } // eslint-disable-next-line @typescript-eslint/no-explicit-any visit(expression) { return expression.accept(this); } /** * Handles unknown expression types by attempting to visit them. * @param {UnknownExpression} expression - An unknown expression type * @returns {Expressions} The processed expression or throws an error */ visitUnknown(expression) { if (expression.accept) return expression.accept(this); throw new Error("unsupported type"); } /** * Processes a format expression by visiting its components. * @template TOutput - The formatted output type * @param {FormatExpression<TOutput>} expression - The format expression to process * @returns {FormatExpression<TOutput>} The processed format expression */ visitFormat(expression) { const source = this.visit(expression.lhs); if (expression.settings) { const settings = this.visit(expression.settings); if (source !== expression.lhs || settings !== expression.settings) { return new FormatExpression(source, expression.formatter, settings); } } if (source !== expression.lhs) return new FormatExpression(source, expression.formatter, null); return expression; } /** * Processes a new expression by visiting its initialization members. * @template T - The expression's value type * @param {NewExpression<T>} expression - The new expression to process * @returns {StrictExpressions} The processed new expression */ visitNew(expression) { const members = this.visitArray(expression.init); if (members !== expression.init) return new NewExpression(...members); return expression; } /** * Processes an apply symbol expression by visiting its components. * @template T - The source expression type * @template U - The result type after applying the symbol * @param {ApplySymbolExpression<T, U>} expression - The apply symbol expression to process * @returns {StrictExpressions} The processed expression */ visitApplySymbol(expression) { const source = this.visit(expression.source); const arg = this.visit(expression.argument); if (source !== expression.source || arg !== expression.argument) { if (!this.isTypedExpression(source)) throw new Error(`Source expression of type ${source?.['type']} cannot be treated as typed`); return new ApplySymbolExpression(source, expression.symbol, arg); } return expression; } /** * Processes a call expression by visiting its components. * @template T - The source expression type * @template TMethod - The method key type * @param {CallExpression<T, TMethod>} expression - The call expression to process * @returns {StrictExpressions} The processed expression */ visitCall(expression) { const source = this.visit(expression.source); const args = this.visitArray(expression.arguments); if (source !== expression.source || args !== expression.arguments) { if (!this.isTypedExpression(source)) throw new Error(`Source expression of type ${source?.['type']} cannot be treated as typed`); return new CallExpression(source, expression.method, args, expression.optional); } return expression; } /** * Processes a member expression by visiting its components. * @template T - The source expression type * @template TMember - The member key type * @param {MemberExpression<T, TMember, T[TMember]>} expression - The member expression to process * @returns {StrictExpressions} The processed expression */ visitMember(expression) { const source = expression.source ? this.visit(expression.source) : expression.source; const member = this.visit(expression.member); if (source !== expression.source || member !== expression.member) { if (!this.isTypedExpression(source)) throw new Error(`Source expression of type ${source?.['type']} cannot be treated as typed`); return new MemberExpression(source, member, expression.optional); } return expression; } /** * Checks if an expression is a typed expression. * @param {Expressions} expression - The expression to check * @returns {expression is TypedExpression<T>} Type predicate indicating if the expression is typed */ isTypedExpression(expression) { return expression != null && (expression.type === ExpressionType.ConstantExpression || expression.type === ExpressionType.ParameterExpression || expression.type === ExpressionType.MemberExpression || expression.type === ExpressionType.ApplySymbolExpression || expression.type === ExpressionType.NewExpression || expression.type === ExpressionType.Format || expression.type === ExpressionType.UnaryExpression || (expression.type === ExpressionType.TernaryExpression && this.isTypedExpression(expression.second) && this.isTypedExpression(expression.third))); } /** * Processes a lambda expression by visiting its body and parameters. * @template T - The lambda's function type * @param {TypedLambdaExpression<T>} expression - The lambda expression to process * @returns {StrictExpressions} The processed expression */ visitLambda(expression) { const parameters = this.visitArray(expression.parameters); const body = this.visit(expression.body); if (body !== expression.body || parameters !== expression.parameters) return new TypedLambdaExpression(body, ...parameters); return expression; } /** * Default equality comparer for items. * @template T - The item type * @param {T} a - First item * @param {T} b - Second item * @returns {boolean} True if items are strictly equal */ static defaultComparer(a, b) { return a === b; } /** * Visits items in an enumerable collection and applies transformation logic. * @template T - The item type * @template U - The transformed item type * @param {IEnumerable<T>} source - Source collection * @param {addToNew} addToNew - Callback to add transformed items * @param {visitSingle} visitSingle - Transformation function per item * @param {EqualityComparer<T>} compare - Optional comparison function */ visitEnumerable(source, addToNew, visitSingle, compare) { const comparator = compare ?? ExpressionVisitor.defaultComparer; let temp = []; let index = 0; for (const item of source) { const transformed = visitSingle.call(this, item, index); if (!comparator(transformed, item)) { if (temp) { temp.forEach(addToNew); temp = null; } } if (temp) temp.push(item); else addToNew(transformed, index); index++; } } /** * Visits an array of expressions and applies transformation logic. * @template T - The input expression type * @template U - The output expression type * @param {T[]} expressions - Array of expressions to visit * @param {preVisit} preVisit - Optional callback before visiting * @returns {U[]} The transformed array of expressions */ visitArray(expressions, preVisit) { let result = null; this.visitEnumerable(expressions, (item, i) => { if (!result) { result = new Array(expressions.length); } result[i] = item; }, (e, i) => { preVisit?.(e, i); return this.visit(e); }); return result || expressions; } /** * Processes a constant expression (no changes required). * @param {ConstantExpression<unknown>} expression - The constant expression * @returns {StrictExpressions} The same expression */ visitConstant(expression) { return expression; } /** * Processes a parameter expression (no changes required). * @param {ParameterExpression<unknown>} expression - The parameter expression * @returns {StrictExpressions} The same expression */ visitParameter(expression) { return expression; } /** * Processes a unary expression by visiting its operand. * @param {UnaryExpression} expression - The unary expression to process * @returns {Expressions} The processed unary expression */ visitUnary(expression) { const operand = this.visit(expression.operand); return operand !== expression.operand ? new UnaryExpression(operand, expression.operator) : expression; } /** * Processes a binary expression by visiting its left/right operands. * @template T - The expression's value type * @param {BinaryExpression<T>} expression - The binary expression to process * @returns {BinaryExpression<Expressions>} The processed binary expression */ visitBinary(expression) { const left = this.visit(expression.left); const right = this.visit(expression.right); return left !== expression.left || right !== expression.right ? new BinaryExpression(left, expression.operator, right) : expression; } /** * Processes a ternary expression by visiting its branches. * @template T - The expression's value type * @param {TernaryExpression<T>} expression - The ternary expression to process * @returns {TernaryExpression<Expressions>} The processed ternary expression */ visitTernary(expression) { const first = this.visit(expression.first); const second = this.visit(expression.second); const third = this.visit(expression.third); return (first !== expression.first || second !== expression.second || third !== expression.third) ? new TernaryExpression(first, expression.operator, second, third) : expression; } } //# sourceMappingURL=expression-visitor.js.map