javascript

Я написал собственный язык программирования на Node.js — и вот что из этого вышло

  • среда, 8 апреля 2026 г. в 00:00:05
https://habr.com/ru/articles/1020258/

Привет, Хабр! Меня зовут SlywerX, я студент 3 курса кафедры Программной инженерии МТУ (Алматы) и fullstack-разработчик. Несколько месяцев назад я задался вопросом: а как вообще работают языки программирования изнутри? Лучший способ разобраться — написать свой. Так появился SWX (Shadow Web eXploit) — скриптовый язык на базе Node.js с собственным синтаксисом, криптографией и даже HTML-рендерингом.

Сейчас SWX на версии 7.0.0. В этой статье расскажу как всё устроено, что было сложно и зачем это вообще нужно было делать.

Зачем писать свой язык?

Честный ответ: из любопытства. Я работаю с JavaScript каждый день, но никогда не понимал что происходит до того как код начинает выполняться. Как интерпретатор понимает что x = 5 это присвоение, а не сравнение? Как работает AST? Что такое лексер?

Все эти вопросы привели меня к проекту decSec и языку SWX. Никакого практического смысла — чистое желание разобраться изнутри.

Архитектура: как устроен SWX

Любой язык программирования проходит через несколько этапов обработки кода. В SWX их три:

Исходный код → [Лексер] → Токены → [Парсер] → AST → [Интерпретатор] → Результат

1. Лексер (lexer.js)

Лексер — это первый этап. Он берёт строку текста и разбивает её на токены — атомарные единицы языка.

Например, строка:

swx x = 42

Превращается в токены:

KEYWORD(swx) IDENT(x) ASSIGN(=) NUMBER(42)

Самое сложное здесь — правильно определить границы токенов. Например, >= это один токен, а не > и = по отдельности. В SWX я использую кастомные операторы типа >~ (больше или равно) и =? (равенство), что потребовало аккуратной обработки многосимвольных последовательностей.

2. Парсер (parser.js)

Парсер берёт токены и строит AST (Abstract Syntax Tree) — дерево которое описывает структуру программы.

Вот как выглядит AST для простого условия:

swx x = 10
sw x >~ 5 {
    wx "больше пяти"
}

json

{
  "type": "IfStatement",
  "condition": {
    "type": "BinaryExpression",
    "operator": ">=",
    "left": { "type": "Identifier", "name": "x" },
    "right": { "type": "NumberLiteral", "value": 5 }
  },
  "body": [
    {
      "type": "PrintStatement",
      "value": { "type": "StringLiteral", "value": "больше пяти" }
    }
  ]
}

Парсер — самая сложная часть проекта. Особенно рекурсивный спуск для выражений с приоритетами операторов. Например, 2 + 3 4 должно вычисляться как 2 + (3 4), а не (2 + 3) * 4.

3. Интерпретатор (interpreter.js)

Интерпретатор обходит AST и выполняет каждый узел дерева. Это называется tree-walking interpreter.

Для каждого типа узла есть своя функция:

  • IfStatement → проверяет условие, выполняет нужную ветку

  • ForLoop → итерирует нужное количество раз

  • FunctionDeclaration → сохраняет функцию в таблице символов

  • BinaryExpression → вычисляет арифметику/логику

Синтаксис SWX — как это выглядит

Я намеренно сделал синтаксис непохожим на существующие языки. Частично для того чтобы разобраться в парсинге нестандартных конструкций, частично — просто для фана.

Переменные и вывод

swx name = "SlywerX"
swx age = 20
wx "Привет, {name}! Тебе {age} лет."

Условия

sw age >~ 18 {
    wx "совершеннолетний"
} dr sw age >~ 13 {
    wx "подросток"
} dr {
    wx "ребёнок"
}

Циклы

xs 5 { wx "итерация {_i}" }      // повторить 5 раз

swx arr = ["a", "b", "c"]
xw item in arr { wx item }        // foreach

xl x > 0 { x -= 1 }              // while

Функции

sx add(a, b = 0) => {
    ws a + b
}

swx result = add(3, 4)
wx result  // 7

Pattern matching (v7.0.0)

match status {
    case 200 => { wx "OK" }
    case 404 => { wx "Not Found" }
    default  => { wx "Unknown" }
}

Pipe оператор

swx result = "hello" |> upper    // HELLO
swx n = arr |> len               // длина массива

Встроенная криптография (#x)

Одна из фишек SWX — встроенный криптографический модуль на базе Node.js crypto. Без внешних зависимостей.

swx hash = #x.sha256("hello")
wx hash  // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

swx encrypted = #x.aes_enc("secret data", "mykey")
swx decrypted = #x.aes_dec(encrypted, "mykey")

swx keys = #x.rsa_keys(2048)
swx signature = #x.rsa_sign("data", keys.private)
wx #x.rsa_verify("data", signature, keys.public)  // true

HTML-рендеринг (sx>)

Ещё одна нестандартная фича — встроенный DSL для генерации HTML прямо из SWX кода:

sx> "output/index.html" {
    @style {
        body   >> bg(#030810) color(#fff) font(Courier New, monospace)
        h1     >> color(#00e5ff) size(24px) bold uppercase
        .btn   >> bg(#00e5ff) color(#000) pad(10px 20px) pointer
    }
    @body {
        div.card >> {
            h1 >> "Привет от SWX"
            button.btn >> (onclick: "alert('работает')") { "Нажми" }
        }
    }
}

Это генерирует полноценный HTML файл с встроенными стилями. Своеобразный Tailwind на минималках.

Что было сложно

1. Приоритеты операторов в парсере Реализовать правильный порядок вычислений через рекурсивный спуск — нетривиальная задача. Пришлось несколько раз переписывать логику.

2. Конфликты в лексере Кастомные операторы типа *~ (умножение с присвоением) конфликтовали с CSS-идентификаторами в HTML-рендерере. Решил через контекстный режим лексера.

3. While-цикл (xl) Бесконечные циклы роняли процесс. Добавил лимит в 1 миллион итераций с понятной ошибкой.

4. Стандартная библиотека Писать stdlib на самом SWX когда интерпретатор ещё не стабилен — это как чинить самолёт в полёте. Но именно это лучше всего показывало баги.

Что дальше

  • #ai модуль — интеграция с LLM API прямо из SWX

  • Компилятор в байткод вместо tree-walking интерпретатора (для скорости)

  • Пакетный менеджер для SWX модулей

  • Подсветка синтаксиса для VS Code

Выводы

Писать свой язык — это лучший способ понять как работают чужие. После этого проекта я совершенно по-другому смотрю на JavaScript, Python и любой другой язык. Каждый if, каждый for — это узел в AST который кто-то написал руками.

Если вы хотите реально понять программирование изнутри — попробуйте написать хотя бы простой интерпретатор. Даже калькулятор с переменными многому научит.

GitHub

Буду рад вопросам и критике в комментариях!

SlywerX — Fullstack Developer, студент METU, кафедра Программной инженерии