JSEP
Как работает парсинг выражений на базе JSEP в Lowcode Platform и почему он выбран в качестве основы для микро-DSL выражений.
🎯 Зачем эта страница
Эта страница объясняет:
- что такое JSEP и почему он используется в платформе;
- как работает его плагинная архитектура;
- как устроена интеграция внутри
@lowcode/expression-parser; - какие синтаксические конструкции JSEP поддерживает «из коробки»;
- какие расширения используются в проекте (object plugin, дополнительные операторы);
- как JSEP вписывается в общую архитектуру DSL и DSL Compiler.
1. Что такое JSEP
JSEP — это минималистичный парсер выражений JavaScript, который преобразует строку в абстрактное синтаксическое дерево (AST).
Он идеально подходит для low-code платформ, так как:
- парсит выражения без выполнения кода (в отличие от
evalилиnew Function); - безопасен — не даёт доступа к глобальным объектам и операторам управления (
if,while,for); - лёгкий — весит всего несколько килобайт и быстро работает в браузере;
- поддерживает плагинную систему (операторы, объекты, new, computed-access);
- хорошо интегрируется с TypeScript.
В отличие от полноценного JS-парсера (Acorn, Esprima), JSEP не обрабатывает полный язык JavaScript — только выражения. Это идеально соответствует нашему DSL: выражения должны быть простыми, безопасными и проверяемыми.
2. Что умеет JSEP
JSEP поддерживает базовый набор конструкций:
Литералы
42
"hello"
true
null
Идентификаторы
state
props
user
Доступ к свойству
obj.prop
obj[expr]
Вызовы функций
fn(x, y)
Бинарные операторы
a + b
x * y
x == y
Логические операторы
a && b
x || y
Унарные операторы
!x
-type
typeof x
Тернарный оператор
cond ? a : b
3. Плагины JSEP
JSEP предоставляет механизм расширения через плагины. Плагин может:
- добавить новый оператор;
- добавить новый тип узла AST;
- изменить разбор существующих конструкций;
- внедрить новую синтаксическую форму.
Lowcode Platform использует:
3.1. @jsep-plugin/object
Этот плагин добавляет поддержку объектных литералов:
{ a: 1, b: state.count }
Без него JSEP воспринимает {} как блок, а не object expression, и выдаёт ошибку.
После подключения плагина JSEP возвращает AST вида:
ObjectExpression
properties:
- (key: Identifier "a", value: Literal 1)
- (key: Identifier "b", value: MemberExpression ...)
Плагин регистрируется в jsep-config.ts внутри пакета @lowcode/expression-parser:
import jsep from 'jsep';
import jsepObject from '@jsep-plugin/object';
export function configureJsep() {
jsep.plugins.register(jsepObject);
jsep.addBinaryOp('??', 3);
return jsep;
}
3.2. Дополнительные операторы
Платформа добавляет оператор ?? (nullish-coalescing), который отсутствует в JSEP по умолчанию.
4. Интеграция с @lowcode/expression-parser
Модуль @lowcode/expression-parser использует JSEP как первый этап трансляции:
Строка → (JSEP) → jsep AST → (convert) → Expression AST
Конвертация происходит в parser/parse.ts:
-
Вызов
jsep(source). -
Рекурсивное преобразование jsep-узлов в собственные узлы Expression AST:
- нормализация типов для DSL;
- перенос позиций
start/end; - единый формат для дальнейшей типизации.
-
Возврат
ParseResult { ast, errors }.
Почему JSEP подходит идеально:
- позволяет реализовать компактный, независимый от JS‑runtime язык выражений;
- не тянет огромные зависимости (в отличие от Babel/Acorn);
- легко расширяется под нужды DSL;
- работает одинаково и в браузере, и в Node.js.
5. Использование JSEP в DSL Compiler и Builder‑web
5.1. В DSL Compiler
@lowcode/dsl-compiler использует выражения для нормализации пропсов:
parseExpression— строит дерево;inferExpressionType— выводит тип результата;validateExpressionType— проверяет корректность типов и доступных переменных;expressionToString— печатает AST в JS‑подобный текст.
Результат сохраняется в AstExpressionValue вместе с:
- AST;
- inferredType;
- usedSymbols.
5.2. В Builder‑web
В редакторе свойств (PropertiesPanel v2):
- ввод пользователя парсится JSEP‑движком на лету;
- ошибки синтаксиса подсвечиваются в UI;
- контекст (
state,props,data) передаётся в валидатор; - результат парсинга участвует в подсветке, автодополнении и валидации.
6. Как расширять синтаксис JSEP
Чтобы добавить новый синтаксис в выражения, можно:
- Подключить дополнительный плагин.
- Добавить собственный плагин.
- Зарегистрировать новый бинарный или унарный оператор:
jsep.addBinaryOp('**', 10); // оператор возведения в степень
- Добавить поддержку нового узла в
convert(...)(parse.ts). - Добавить правила вывода типа в
inferType.ts. - Добавить проверки в
validate.ts. - Добавить тесты.
Примеры возможных расширений:
- безопасные функции (
len(x),sum(list),avg(list)); - работа с датами (
now(),today()); - специальные операторы DSL.
7. Преимущества JSEP для Lowcode Platform
- лёгкий и быстрый для браузера;
- безопасен по умолчанию (нет eval, нет исполнения);
- легко расширяем;
- идеально подходит для «выражений», а не полного JS;
- позволяет разделить уровни:
- парсинг → AST,
- анализ → типы,
- компиляция → React/HTML.
Именно поэтому JSEP является идеальной основой микро‑DSL выражений в Lowcode Platform.
8. Резюме
JSEP — это фундамент движка выражений, обеспечивающий:
Единый синтаксис
Расширяемую архитектуру
Безопасный парсинг без выполнения
Совместимость с DSL Compiler и Builder‑web
Он лёгок, предсказуем и прост в интеграции — ровно то, что нужно для low-code платформы, где выражения должны быть безопасными, удобными и легко анализируемыми.