Пока индустрия движется в сторону усложнения компиляторов, я задался вопросом: можно ли создать инструмент, который дает безопасность Rust, гибкость C и при этом не весит сотни мегабайт?

Так появился Flame — системный язык с фронтэндом в 260 КБ, который реализует управление памятью через статический анализ AST и предлагает альтернативный взгляд на обработку ошибок через патчинг дерева токенов.

Почему это не «очередной клон Си»?

Основные проблемы системных языков сегодня — это либо риск утечек (C/C++), либо избыточная сложность анализаторов (Rust). В Flame я реализовал три концепции, которые решают эти задачи иначе:

1. Автоудаление помеченных переменных в куче

Вместо сложного Borrow Checker как в Rust, Flame использует статический анализ AST по специальному алгоритму и вставляет инструкцию удаления. Алгоритм работает так:

  1. Компилятор сканирует с конца;

  2. Когда находит инструкцию, где использованная помеченная autodel — вставляет после этой инструкции удаление;

Циклы считаются как одна инструкция, если переменная используется в последний раз в цикле и объявлен вне цикла — удалится после цикла. Если объявлена внутри цикла — удалится внутри цикла. А ветвления в виде if/else проверяются, и вставляется удаление в каждый блок где использована в последний раз. При передачи переменной по возврату функции или метода — наследуется autodel, но если при передаче используется только значение а не сам адрес — то создастся промежуточная переменная, потом вставится удаление, потом промежуточная передастся в возврат. Данная возможность не до конца проверена, и будет укрепляться и развиваться.

2. Meta-exceptions: патчинг токенов

Вместо тяжёлых try/catch, Flame полуил свой подход. В языке исключение описывается в специальной структуре, где объявляются: места где может появиться исключение, и проверка/замена. Когда компилятор видит последовательность токенов которые описаны каким то исключением — он обращается к этой структуре, и либо откатывается до завершаения прошлой инструкции (';' или '{' или '}'), и вставляет проверку, либо полностью вырезает эту инструкцию и вставляет замену (при описании замены можно вставлять исходную инструкцию с помощью $source). Это позволяет исключить необходимость try/catch и не засорять код if/else, но сохранить возможность перехватывать любые исключения.

Устройство компилятора

Компилятор (по словам GitHub) содержит: 83.6% кода на Си, и 16.4% Shell и LLVM (отладка и тестирование). Сам фронтэнд весит 260 КБ.

В процесс компиляции входят несколько этапов:
1. Препроцессор (#include, #ifdef и прочее)
2. Лексер (разбирает текст на токены)
3. Препарсер (работает с исключениями)
4. Парсер (разбирает токены на AST)
5. Прегенератор (работает с безопасностью памяти)
6. Генератор (разбирает AST на LLVM IR и компилирует в.o, линкует через GCC)

Cсылка на GitHub с исходным кодом

Примеры кода

В основном синтаксис один в один как в Си, но переменные и поля классов объявляются с ключевым словом var, а методы и функции через func.

Прмер объявления класса:

class Name {
  var int a;         //числа имеют свой суффикс типа: 5 - int; 5s - short; 5l - long;
  var short b = 55s;

  func void c(int ab, int bb) {
    self->a = ab + bb;
  }

  new() {         //конструктор
    self->a = 5 + 5;
  }

  delete() {      //деструктор
    self->a = 0;
  }
}

Пример автоудаления:

func int main() {
  var autodel Name *obj = new();
  obj->c(5, 5);
  // <- тут будет delete() obj;
  return 0;
}

Пример описания исключений:

exception DivByZero {
  var int op;
  instruction {
    %n / %:op        //%n - любое число, %i - идентификатор
  }                  //%k - ключевое слово, %: - присваивание значения в переменную

  check {            //есть replace, он полностью заменяет инструкцию на то что 
    if (op == 0) {   //op инициализируется в коде автоматически
      //...
    }
  }
}

Остальные можно найти в README.md в GitHub

Будущее Flame

Flame будет активно разваиваться, улучшать совместимость с другими языками (через настройки компилятора @callconv, который позволит менять последовательность регистров для вызова функции) и добавлять новые фичи (Hot Reload или Restart через функцию из стандартной библеотеки которая через общую память и родительский процесс прогонит данные в новый процесс через таблицу имён). Скоро выйдет документация по языку.

Заключение

Flame — это язык, где я реализовал концепцию язык, который удобный, но мощный системный язык. Язык решает проблемы которые есть в других языках: перегруженность, сложность, медлительность, опасность. Но я не могу сказать, что язык не имеет минусов. Впереди ещё много работ над стабильностью и развитием!