Коротка відповідь
middleInterpreter — це патерн, що визначає граматику для мови і реалізує інтерпретатор, який обробляє вирази. У TS створюємо ієрархію класів: `Expression`, `TerminalExpression`, `NonterminalExpression`. Кожен `Expression` має метод `interpret(context: string): number`, який повертає результат обчислення. Приклад: `NumberExpression` повертає число, а `AddExpression` обчислює суму двох підвиразів.
Повне пояснення
1. Що це і навіщо
Interpreter – патерн розділення синтаксису мови від її виконання. Він корисний, коли треба обробляти прості DSL‑и, правила конфігурацій або вирази у вигляді рядків.
2. Принцип роботи / алгоритм
Патерн складається з:
- Context – рядок, що інтерпретуємо;
- AbstractExpression – базовий клас/інтерфейс з методом
interpret(context); - TerminalExpression – вузли, що повертають конкретні значення (число, змінна);
- NonterminalExpression – вузли, що комбінують інші вирази (операції, умови).
Алгоритм: парсер створює дерево виразів, а інтерпретатор рекурсивно викликає
interpretпо дереву.
3. Реалізація (JS / TS)
// базова реалізація
interface Expression {
interpret(context: string): number;
}
class NumberExpression implements Expression {
constructor(private value: number) {}
interpret() { return this.value; }
}
class AddExpression implements Expression {
constructor(private left: Expression, private right: Expression) {}
interpret(context: string) {
return this.left.interpret(context) + this.right.interpret(context);
}
}
// реалістичний приклад: простий калькулятор для рядка "3+5"
const expr = new AddExpression(new NumberExpression(3), new NumberExpression(5));
console.log(expr.interpret('')); // 8
// спрощений для новачка: інтерпретатор логічних виразів
class AndExpression implements Expression {
constructor(private left: Expression, private right: Expression) {}
interpret(context: string) {
return this.left.interpret(context) && this.right.interpret(context);
}
}
4. Тестування (Jest або Vitest)
import { AddExpression, NumberExpression } from './interpreter';
test('AddExpression returns sum', () => {
const expr = new AddExpression(new NumberExpression(2), new NumberExpression(3));
expect(expr.interpret('')).toBe(5);
});
5. Проблеми та edge cases
- Неправильне використання ролей – змішування
TerminalіNonterminalу одному класі. - Зайва абстракція – створення надлишкових класів для простих виразів.
- Cyclical dependencies – рекурсивні вирази без базового випадку призводять до стек‑overflow.
- Плутанина між Interpreter і Visitor – обидва працюють з деревом, але інтерпретатор повертає значення, а Visitor – виконує операції.
- Performance‑пастки – інтерпретація великого дерева у циклі може бути повільною; кешування результатів допомагає.
6. Оптимізація та продуктивність
- Кешування результатів
interpretдля повторних викликів. - Використання ітеративного обходу замість рекурсії для глибоких дерев.
- Підтримка асинхронних виразів через
Promiseу методіinterpret.
7. Без JS / архітектурні альтернативи
- У браузері можна реалізувати інтерпретатор через Web Workers для ізоляції.
- У серверних мовах (Java, C#) патерн часто замінюють ANTLR або інші парсери.
- У чистій архітектурі без патерна можна просто писати рекурсивні функції, але це порушує розділення відповідальності.
8. Cheatsheet + follow-up
- Cheatsheet:
Expression→interpret(context);Terminalповертає значення,Nonterminalкомбінує. - Interview Q1: Як уникнути циклічних залежностей у дереві виразів?
- Interview Q2: Які переваги Interpreter над Visitor у випадку обчислення виразів?