Skip to main content

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:

  1. Вызов jsep(source).

  2. Рекурсивное преобразование jsep-узлов в собственные узлы Expression AST:

    • нормализация типов для DSL;
    • перенос позиций start/end;
    • единый формат для дальнейшей типизации.
  3. Возврат 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

Чтобы добавить новый синтаксис в выражения, можно:

  1. Подключить дополнительный плагин.
  2. Добавить собственный плагин.
  3. Зарегистрировать новый бинарный или унарный оператор:
jsep.addBinaryOp('**', 10); // оператор возведения в степень
  1. Добавить поддержку нового узла в convert(...) (parse.ts).
  2. Добавить правила вывода типа в inferType.ts.
  3. Добавить проверки в validate.ts.
  4. Добавить тесты.

Примеры возможных расширений:

  • безопасные функции (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 платформы, где выражения должны быть безопасными, удобными и легко анализируемыми.