The Jerusalem PostANU adds three new artworks to 'October 7' exhibit as Israel marks third anniversary of massacrePunchOne dead, many injured as bus catches fire on Kwara expresswayESPNTransfer rumors, news: Bayern brace for tough contract talks with OliseBollywood HungamaRakul Preet Singh BREAKS silence on Income Tax searches, DENIES involvement in illegal foreign remittances: "Have been paying my due taxes since the age of 20"InquirerFoul odor prompts shoreline inspection in MamburaoInvesting.comGoldman Sachs anticipe des gains pour le real brésilien après les électionsInvesting.comGoldman Sachs prevé ganancias del real brasileño tras eleccionesNumeramaL’IA a tué (pour l’instant) l’un des programmes de sécurité de GoogleBBC NewsScott out of England squad and faces injury lay-off7sur7Trump dévoile le numéro de portable d’un collègue de son parti opposé à l’heure d’été permanente: “Appelez-le!”Times of India EntertainmentSRK's net worth is Rs 12,000 crore which is 4x times more than Salman, Aamir, Akshay, says analystGMA NewsSara Duterte had over P10M in withdrawals, check encashment in Dec. 2024 -- AMLC witness
The Daily Newsstand · Free, Always
Monday, October 5, 2026

Сфера — полностью кириллический язык программирования: от «Привет, мир» до HTTP‑сервера и bare‑metal

Translate

На GitHub лежит репозиторий Сфера — это полностью кириллический язык программирования. Компилятор написан на Rust, код генерируется через LLVM 18 в нативный машинный код. Не интерпретатор, не транспайлер в C — а настоящий компилятор: лексер → парсер → LLVM IR → машинный код.

Язык прошёл путь от «вывести строку» до собственного HTTP‑сервера, хранилища данных, системы логов/метрик/трейсинга, графики через SDL2, полноценного отладчика с DWARF, пакетного менеджера со своим публичным индексом и — последнее по времени добавление — опционального детерминированного планировщика потоков. Эта статья — это честный разбор на данный промежуток времени: что работает, как устроено и чего пока нет.

Синтаксис

Полностью кириллический — все ключевые слова, имена встроенных функций и типов пишутся кириллицей. Не «русский Pascal» и не «русский Python» — свой синтаксис, по духу ближе к Swift/Rust.

Концепция

Сфера

Swift

Rust

Функция

функция

func

fn

Переменная

целое х = 5;

var x = 5

let x = 5;

Константа

неизменно целое х = 5;

let x = 5

const x = 5;

Если

если (х > 0) { }

if x > 0 { }

if x > 0 { }

Иначе

иначе { }

else { }

else { }

Цикл while

пока (х < 10) { }

while x < 10 { }

while x < 10 { }

Возврат

вернуть х;

return x

return x;

Структура

структура

struct

struct

Интерфейс

контракт

protocol

trait

Ошибка

результат<T>

Result<T, E>

Result<T, E>

Self

это

self

self

Bool

верно (да/нет)

Bool (true/false)

bool (true/false)

Программа «Привет, мир»:

функция главная() -> целое {
    вывести("Привет, мир!");
    вернуть 0;
}

Компиляция и запуск (одной командой, как go run):

запустить привет.код          # соберёт и сразу запустит
запустить привет.код -c       # только соберёт, без запуска

Флаг -c можно писать и кириллицей — -с. Оба варианта равнозначны, и сделано это сознательно: кириллическая «с» и латинская «c» выглядят неотличимо на экране, но для компьютера это разные символы. Поддержка обоих — чтобы привычная латинская -c продолжала работать без сюрпризов.

Типы данных

Целые числа

8 типов от целое8 (8 бит) до целое64 / целое (64 бита), плюс беззнаковые варианты беззнак8—беззнак64.

Важное правило: типы разной разрядности не смешиваются неявно. целое32 и целое в одном выражении требуют явного приведения. Даже инициализация литералом — целое32 а = целое32(5);, не целое32 а = 5;. Строго, но безопасно.

Дробные числа

дробное (64 бита по умолчанию), дробное32, дробное64. Полностью рабочая арифметика (+, -, *, /, сравнения, унарный минус), вывод (вывести(дробное)) и дробное_в_текст.

Текст

текст — строка в двойных кавычках. Конкатенация через +. Экранирование: \n, \r, \t, \", \\. Сравнение по содержимому, не по адресу.»

Логический тип

верно — булев тип. Литералы — да и нет, не true/false.

Переменные, вывод типа и неизменяемость

Объявление с явным типом: тип имя = значение;. Но тип можно и опустить — если имя ещё не объявлено, имя = значение; компилятор понимает как объявление с выводом типа, а не присваивание:

возраст = 25;          // выведено: целое
имя = "Иван";            // выведено: текст
итог = удвоить(21);      // выведено из типа возврата функции

Работает для литералов, вызовов функций/методов, конструкторов структур — для подавляющего большинства реальных объявлений. Не работает для Список<T>/Словарь<К, З> (нужен явный тип параметра) и не сочетается с неизменно (та по‑прежнему требует явного типа).

Неизменяемость — через неизменно (только с явным типом):

неизменно целое максимум = 100;

Явное приведение типов — имя типа как функция: целое32(значение), дробное(значение).

Операторы

Арифметика: +, -, *, /, % (остаток от деления).

Сравнение: ==, !=, <, >, <=, >=.

Логические: &&, ||, !.

Побитовые: &, |, ^, ~ — только для целых чисел, оба операнда одного типа. Приоритет ниже, чем у сравнения (как в C/Rust) — а & б == 8 разбирается как а & (б == 8). При смешивании используйте скобки.

Сдвиги: << и >> тоже есть, несмотря на то, что язык использует </> для обобщённых типов (Список<целое>). Риск был в том, что вложенные обобщённые (Список<Список<целое>>) дают два подряд идущих < — та же ловушка, что была в ранних версиях C++. Решение: лексер не трогали (он по‑прежнему выдаёт </> раздельно) — сдвиг распознаётся только на уровне разбора выражений, где два соседних токена </> не могут означать ничего, кроме сдвига; закрытие вложенных обобщённых типов — совсем другой, не пересекающийся с этим код. Строгость та же, что у остальных побитовых: только целые, оба операнда одного типа.

Строковая интерполяция

Внутри текстового литерала $имя подставляет значение переменной, а ${выражение} — значение любого выражения формируемого типа (текст, любые целые, дробное, верно):

целое возраст = 25;
текст имя = "Никита";
вывести("Привет, $имя! Тебе $возраст лет.");
вывести("Через год будет ${возраст + 1} лет.");

// Результат интерполяции — обычное текстовое значение: можно сохранить
// в переменную, вернуть из функции, склеить через "+".
текст сообщение = "Итог: ${площадь(3, 4)} м²";

Честное ограничение: строковые литералы внутри ${...} не поддерживаются — лексер разбирает внешний литерал целиком до того, как парсер узнаёт о вложенном выражении.

Управляющие конструкции

Условие

если (х > 10) {
    вывести("Больше десяти");
} иначе если (х > 0) {
    вывести("Положительное");
} иначе {
    вывести("Ноль или отрицательное");
}

Цикл с условием

целое счётчик = 0;
пока (счётчик < 5) {
    вывести(целое_в_текст(счётчик));
    счётчик = счётчик + 1;
}

Цикл по коллекции

целое[3] числа = [1, 2, 3];
для каждого элемент в числа {
    вывести(целое_в_текст(элемент));
}

для каждого работает и с Список<T> — тип переменной цикла выводится автоматически из типа элемента списка, работает и с пустым списком, и со структурами‑элементами, прервать/продолжить — как обычно.

прервать и продолжить

Обычные break/continue — работают в любом цикле, включая вложенные и тело параллельно { ... }.

Функции

функция сложить(целое а, целое б) -> целое {
    вернуть а + б;
}

Тип возврата указывается через ->. Компилятор проверяет количество и типы аргументов при каждом вызове — и для обычных функций, и для функций из подключённых модулей.

Обобщённые функции

Поддержаны, с честными ограничениями:

функция макс_из_двух<Т>(Т а, Т б) -> Т {
    если (а > б) {
        вернуть а;
    }
    вернуть б;
}

функция главная() -> целое {
    целое итог_целое = макс_из_двух<целое>(3, 7);
    дробное итог_дробное = макс_из_двух<дробное>(2.5, 1.5);
    вернуть 0;
}

Ограничения: только один обобщённый параметр, тип указывается явно при вызове, только функции главного файла.

Массивы

Фиксированного размера, размер — часть типа. Требуют явной инициализации литералом:

целое[5] числа = [10, 20, 30, 40, 50];
числа[0] = 99;
вывести(целое_в_текст(числа[0]));

Структуры

структура Точка {
    целое х;
    целое у;

    функция сумма_координат() -> целое {
        вернуть это.х + это.у;
    }
}

Точка точка = Точка(3, 4);
вывести(целое_в_текст(точка.сумма_координат()));   // 7
  • Поля — без значений по умолчанию

  • Методы — функции внутри тела структуры

  • это — ссылка на текущий экземпляр (аналог self/this)

  • Конструктор генерируется автоматически по всем полям в порядке объявления

  • Структуры — всегда объекты в куче, передаются по ссылке, с ARC

Слабые ссылки

Поле, объявленное как слабая Тип, не увеличивает счётчик ссылок — нужно для разрыва циклических ссылок:

структура Держатель {
    текст имя;
    слабая Цель цель;
}

Проверка живости — метод .жива(). Работает безопасно даже после того, как цель «логически» умерла: пока жива хотя бы одна слабая ссылка, компилятор откладывает реальное освобождение памяти, чтобы жива() могла безопасно проверить.

Инкапсуляция

публично/приватно — поля и методы могут быть скрыты внутри структуры.

Обработка ошибок — результат

Вместо исключений — обёртка результат<T> с тремя полями: .успешно (верно), .значение (тип T), .сообщение (текст ошибки).

функция разделить(целое а, целое б) -> результат<целое> {
    если (б == 0) {
        вернуть ошибка("деление на ноль");
    }
    вернуть успех(а / б);
}

функция главная() -> целое {
    результат<целое> итог = разделить(10, 2);
    если (итог.успешно) {
        вывести(целое_в_текст(итог.значение));
    } иначе {
        вывести("Ошибка: " + итог.сообщение);
    }
    вернуть 0;
}

Многие встроенные функции (подстрока, найти, прочитать_файл) возвращают результат<T> — .успешно нужно проверять перед использованием .значение.

Обобщённые типы

Список

Список<целое> числа = Список<целое>();
числа.добавить(10);
числа.добавить(20);
целое первое = числа.получить(0);
целое количество = числа.размер();
числа.удалить(0);   // удаляет элемент по индексу

Словарь

Словарь<текст, целое> возрасты = Словарь<текст, целое>();
возрасты.положить("Аня", 25);
результат<целое> возраст = возрасты.получить("Аня");
если (возраст.успешно) {
    вывести(целое_в_текст(возраст.значение));
}
возрасты.освободить();   // Словарь не участвует в ARC — освобождать явно

Важное: Словарь.получить(ключ) возвращает результат<Значение>, а не значение напрямую — ключа может не быть. Словарь не участвует в ARC — .освободить() нужен для явного освобождения памяти; текстовые ключи и значения при этом безопасно освобождаются как самостоятельные копии.

Пользовательские обобщённые структуры

структура Коробка<Т> {
    Т содержимое;
}

коробка = Коробка<целое>(42);              // вывод типа
Коробка<целое> другая = Коробка<целое>(7); // явный тип — тоже работает

Параметров может быть и несколько — структура Пара<А, Б> { А первый; Б второй; }, Пара<целое, текст>(42, "привет") — работает и с выводом типа, и с явным типом слева, так же, как одно‑параметрический случай выше.

Список<T> и Словарь<K, V> можно использовать как поле структуры, с цепочками доступа это.поле.метод(...) — в том числе параметризованные СОБСТВЕННЫМИ параметрами структуры (структура ПараСписков<А, Б> { Список<А> первые; Список<Б> вторые; }).

Контракты и полиморфизм

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

контракт Печатаемый {
    функция напечатать() -> текст;
}

структура Кот : Печатаемый {
    функция напечатать() -> текст { вернуть "Мяу"; }
}

структура Собака : Печатаемый {
    функция напечатать() -> текст { вернуть "Гав"; }
}

функция озвучить(Печатаемый существо) -> целое {
    вывести(существо.напечатать());
    вернуть 0;
}

функция главная() -> целое {
    Кот кот = Кот();
    Собака пёс = Собака();
    озвучить(кот);   // "Мяу"
    озвучить(пёс);   // "Гав" — та же функция, другая реализация
    вернуть 0;
}

Список<Контракт> — коллекция разнотипных объектов через общий интерфейс:

Список<Печатаемый> зоопарк = Список<Печатаемый>();
зоопарк.добавить(кот);
зоопарк.добавить(пёс);

Контракт как тип возврата — функция может быть объявлена как функция ... -> ИмяКонтракта, возвращая любую структуру, реализующую этот контракт; вызывающий код не знает заранее, какая структура за ним стоит.

Оба сценария доведены до рабочего состояния (в т.ч. исправлен настоящий сегфолт при упаковке структуры в контракт внутри списка). ✅ Ограничение из более ранних версий этого материала — что ARC не «видит насквозь» структуру, упакованную в толстый указатель контракта, — с тех пор устранено: элементы Список<Контракт> теперь корректно захватываются и освобождаются (рантайм‑сравнение адреса vtable с известными vtable контракта), и отдельно исправлена утечка локальных переменных контрактного типа при выходе из области видимости (вместе с риском двойного освобождения, который эта же правка могла бы создать при копировании контрактных переменных). Проверено под valgrind — ноль ошибок, ноль утечек.

Многопоточность

параллельно

Запускает блок кода в отдельном потоке ОС. Тело блока не может использовать переменные из окружающей области видимости — защита от обращения к освобождённой памяти.

параллельно {
    вывести("Работаю в фоне");
}

дождаться_все_потоки() и дождаться_поток(номер)

Ждёт завершения всех потоков — или одного конкретного по номеру.

параллельно { вывести("Поток 1"); }
параллельно { вывести("Поток 2"); }
дождаться_все_потоки();
вывести("Оба потока точно закончили работу");

канал

Безопасная передача одного значения из фонового потока:

канал<целое> канал_результата = создать_канал();
параллельно {
    целое вычислено = 2 + 2;
    канал_результата.отправить(вычислено);
}
целое значение = канал_результата.получить();   // ждёт, пока не придёт
канал_результата.освободить();

Каналы с текстом (канал<текст>) тоже поддержаны — по пути найдена и исправлена настоящая гонка данных (use‑after‑free между потоками), не просто утечка: теперь отправить(...) всегда делает независимую копию значения. У канал<T> появился .освободить() для самого объекта канала.

Остающееся ограничение: только целочисленные, указательные и текстовые типы, одностороннняя разовая передача одного значения.

параллельно детерминировано {... } — детерминированный планировщик («эстафета»)

Обычный параллельно — это честные, независимые потоки ОС: относительный порядок их побочных эффектов (кто и когда напечатал строку) непредсказуем и меняется от запуска к запуску — так и должно быть для настоящих потоков без собственного планировщика.

Для задач, которым нужен ВОСПРОИЗВОДИМЫЙ порядок (встроенные/промышленные сценарии, для которых язык и задумывался — станки с ЧПУ, конвейеры, тестирование), добавлен опциональный модификатор, не меняющий поведение обычного параллельно:

функция главная() {
    параллельно детерминировано {
        целое i = 0;
        пока (i < 5) {
            вывести("А" + целое_в_текст(i));
            i = i + 1;
        }
    }
    параллельно детерминировано {
        целое i = 0;
        пока (i < 5) {
            вывести("Б" + целое_в_текст(i));
            i = i + 1;
        }
    }
    дождаться_все_потоки();
}

Вывод этой программы бит‑в‑бит одинаков при любом числе повторных запусков: А0 Б0 А1 Б1 А2 Б2 А3 Б3 А4 Б4.

Механизм — «эстафета» (baton over real threads): все детерминировано‑потоки одной партии (партия — это потоки между соседними вызовами дождаться_все_потоки(), тот же принцип, что уже был у обычных потоков) по кругу передают друг другу право выполняться, как эстафетную палочку, на заранее определённых точках: начало каждой итерации пока, после вывести(...), после канал.отправить/.получить. Честно: реальной параллельности между такими потоками поэтому нет — в каждый момент физически работает ровно один из них. Обычный параллельно можно свободно запускать рядом — он в эстафете не участвует и её не блокирует.

По пути найдены и исправлены три отдельных, по‑настоящему воспроизводимых гонки/дедлока — ни один не был виден по чтению кода, только многократным живым прогоном (сотни повторов, в т.ч. под valgrind):

  1. Счётчик партии не сбрасывался между батчами — вторая партия зависала навсегда.

  2. Запись родителя в общий флаг активности потока — без мьютекса, редкая (~1 из 5 запусков) гонка видимости памяти, тоже дедлок.

  3. Самый тонкий: быстрый поток мог полностью отработать и покинуть эстафету ДО того, как родитель успевал зарегистрировать следующего участника — не зависание, а молчаливая потеря самого чередования (Г0 Г1 Г2 Д0 Д1 Д2 вместо честного Г0 Д0 Г1 Д1 Г2 Д2). Исправлено барьером регистрации всей партии.

Честные ограничения v1: для каждого не является точкой передачи (обходной путь — пока со счётчиком); статический барьер регистрации партии работает только для последовательности параллельно детерминировано, идущей сразу друг за другом в одном блоке (не внутри если/цикла — там действует более слабый, задокументированный запасной вариант); максимум 256 потоков на партию; главный поток в эстафете не участвует.

Управление памятью (ARC)

Структуры управляются автоматически через подсчёт ссылок — free/delete писать не нужно. Текст в подавляющем большинстве практических случаев тоже под управлением ARC: локальные переменные, конкатенация, текстовые поля структур, результат функции/метода без промежуточной переменной (вывести(целое_в_текст(42)); теперь не течёт), результаты Список<текст>/Словарь<К,З>, элементы, переданные напрямую в .добавить()/.положить(), и текст, полученный через канал<текст>.получить().

✅ Ранее здесь был честно задокументирован отдельный случай — структура, упакованная в контракт (не крах, только утечка). С тех пор исправлен (см. раздел про контракты выше) — сейчас известных дыр в ARC не осталось.

Модули

подключить утилиты;

Поиск по трём уровням:

  1. {имя}.код рядом с подключающим файлом

  2. {корень_проекта}/пакеты/{имя}.код (в т.ч. составное имя пакеты/ИМЯ_ПАКЕТА/файл.код — см. пакетный менеджер ниже)

  3. Системная стандартная библиотека (при установке через собрать.sh или установить-windows.ps1)

Доступ к содержимому — напрямую по имени или через квалификацию модуля:

целое итог = удвоить(21);            // обычный вызов
целое итог2 = утилиты.удвоить(21);  // с указанием модуля

Как это устроено: не раздельная компиляция с изоляцией, а текстовое склеивание перед семантическим анализом. Дублирующиеся имена дают понятную ошибку компиляции, а не молчаливую подмену. Циклические подключения корректно обрабатываются — файл, уже разобранный, пропускается при повторной встрече.

Пакетный менеджер (сфера_пакеты)

Отдельный инструмент для установки стороннего кода — git как реестр, без своей инфраструктуры вроде crates.io/npm: зависимость — это ссылка на git‑репозиторий.

Манифест сфера.зависимости в корне проекта:

геометрия = git https://github.com/кто-то/геометрия-сфера.git
графика_доп = git https://github.com/кто-то/графика-доп.git ветка=разработка описание="Доп. примитивы рисования"
сфера_пакеты установить     # клонирует и ставит все зависимости из манифеста
сфера_пакеты список         # показывает установленные пакеты и как их подключать
сфера_пакеты удалить ИМЯ    # убирает пакет целиком

Изоляция пространств имён. Каждый пакет ставится в свою папку — пакеты/ИМЯ_ПАКЕТА/. Два разных пакета могут содержать одноимённые файлы без конфликта — физически они в разных папках, подключаются по составному имени подключить ИМЯ_ПАКЕТА.файл;.

Транзитивные зависимости. Если у устанавливаемого пакета внутри его собственного репозитория тоже есть сфера.зависимости, сфера_пакеты установить подхватывает и его — обходом в ширину по всему графу, не только один уровень. Циклы (А зависит от Б, Б — от А) корректно обрываются.

Разрешение версионных конфликтов — настоящий constraint solving. Необязательное поле версия=X.Y.Z (вместо ветка=) даёт сравнимую семантическую версию, с поддержкой диапазонов в стиле npm/cargo — ^1.2.0 (совместимые версии) и ~1.2.0 (только патчи). Если один и тот же пакет транзитивно запрошен несколько раз с разными требованиями, они не «побеждают по старшинству», а пересекаются как интервалы версий — итоговая версия обязана удовлетворять всем требованиям дерева одновременно; если пересечение пусто, установка честно падает с ошибкой, называющей оба требования и через какие пакеты каждое пришло.

Файл блокировки (сфера.блокировка). Диапазоны ^/~ нестабильны во времени — у пакета могут появиться новые теги. После каждого успешного установить блокировка перезаписывается с уже разрешёнными точными тегами (как Cargo.lock/package-lock.json) — следующий установить берёт версию из неё напрямую, без похода в сеть, пока она всё ещё удовлетворяет диапазону манифеста.

Публичный индекс пакетов. Раньше, чтобы поставить чужой пакет, нужно было заранее знать его git‑адрес. Индекс закрывает это — один git‑репозиторий с каталог‑файлом, который можно искать и добавлять:

сфера_пакеты индекс-настроить URL_ИНДЕКСА   # один раз на машину
сфера_пакеты индекс-обновить

сфера_пакеты искать математика     # ищет по имени и описанию
сфера_пакеты добавить математика   # дописывает строку в сфера.зависимости
сфера_пакеты установить

Честно: сам индекс — сознательно временное решение, только git, без сервера и базы данных — следующий шаг (когда будет домен и сервер) заменит его полноценным веб‑сервисом. Разрешение версий ограничено форматом X.Y.Z (без пререлизов/build‑метаданных и без >=/<=/hyphen‑диапазонов из npm) и работает только для пакетов с одного и того же git‑адреса.

Внешние функции (FFI)

Прямой вызов функций из C‑библиотек:

внешняя функция получить_переменную_окружения(текст имя) -> текст как "getenv";

Строка после как — настоящее имя символа в библиотеке. Если совпадает с именем в Сфере, можно опустить.

Кириллические имена FFI‑функций — ключевая возможность для полностью кириллического проекта:

внешняя функция инициализировать(беззнак32 флаги) -> целое32 как "SDL_Init";

Программист вызывает инициализировать(32) — полностью по‑русски. Латинское имя "SDL_Init" существует только в одной строке объявления, которую пишет автор привязки, а не пользователь библиотеки.

Флаг --линковать ИМЯ добавляет -lИМЯ к линковке.

Отладчик

Флаг -g (или --отладка) встраивает в объектный файл настоящую DWARF‑отладочную информацию через LLVM — тот же механизм, которым пользуется clang для C/C++. Отдельного инструмента отладки не появляется — запускается обычный lldb или gdb, как для любой скомпилированной программы:

bash

запустить программа.код -g -c
lldb ./программа
# b программа.код:10   — точка останова по номеру строки
# run / next / step / bt / continue

Работает без ограничений: точки останова по номеру строки для любой функции/метода (включая тело параллельно), пошаговое выполнение, полноценный стек вызовов с настоящими именами функций «Сферы», просмотр исходного кода в отладчике. Проверено напрямую и lldb, и gdb.

Честное ограничение: просмотр значений локальных переменных по имени (print х) пока не поддержан — не описан точный DWARF‑тип для каждого типа языка. lldb/gdb всё равно показывают сырые байты по адресу.

Компиляция под другую архитектуру процессора

Кросс‑компиляция под другие архитектуры (ARM64 и др.) через LLVM.

✅ Компилятор полностью работает внутри Android‑приложения — теперь это подтверждённый факт, не осторожное предположение. Кросс‑компиляция под Android (--целевая-платформа aarch64-linux-android21 + --android-sysroot) проверена с реальным запуском результата на физическом Android‑устройстве через Termux. Но интереснее другое: сам компилятор запустить, линковщик ld.lld и небольшая Sfera‑замена clang кросс‑собраны под ARM64 и зашиты в APK как файлы lib/arm64-v8a/*.so — Android распаковывает всё из lib/<abi>/*.so с правом на исполнение, это легальный, документированный приём (не эксплойт), который снимает необходимость в root или Termux для запуска. Внутри приложения запустить вызывается как обычная команда ОС — на Android это работает, вопреки более раннему предположению в этом материале, что прямого доступа к оболочке нет. Подтверждено adb logcat, многократно: полный цикл компиляция → линковка → запуск результата, целиком на устройстве, с настоящим выводом собранной программы.

⚠️ Единственное, что по‑прежнему честно не проверено вживую — сборка этой возможности ВМЕСТЕ с интерфейсом панели‑приложения (компиляция и запуск по отдельности проверены, собранные воедино — ещё нет).

Экспериментальная поддержка bare‑metal

Запуск без операционной системы, на голом железе. Раньше программа работала без libc, но запускалась ядром Linux. Здесь — прошивка (BIOS/UEFI) загружает программу напрямую.

Архитектурное решение — Multiboot вместо своего загрузчика. Проверено через QEMU.

Сеть — TCP‑сокеты и HTTP‑сервер

Прямой доступ к POSIX‑сокетам через FFI. Не встроенный HTTP‑клиент — а «кирпичики» (socket, connect, send, recv, close, bind, listen, accept, setsockopt), из которых HTTP собирается на самой Сфере.

HTTP‑клиент

подключить сеть;
целое32 сокет = создать_сокет(целое32(2), целое32(1), целое32(0));
// connect, send, recv — всё на самой Сфере

Проверено: настоящая программа на Сфере подключилась к локальному HTTP‑серверу, получила 240 байт — точно совпало с суммой заголовков (185) и тела (55).

HTTP‑сервер

Полноценный сервер: принимает подключение, читает запрос, извлекает первую строку через найти/подстрока, отвечает валидным HTTP‑ответом. Маршрутизация — разные ответы для разных путей. Параллельная обработка подключений через параллельно.

Проверено объективно: curl получил корректные ответы от всех маршрутов. Три параллельных запроса — три разных ответа, одновременно.

Ограничения

  • Только TCP, нет UDP

  • Нет DNS‑резолвинга — только числовой IP

  • Нет HTTPS/TLS

  • Нет URL‑декодирования

Графика и UI (SDL2)

Библиотека графика.код — обёртка над SDL2 через FFI. Подключается как обычный .код‑файл: подключить графика;.

Что работает:

  • Создание окна, холста, закраска экрана

  • Обработка событий — клики мыши, ввод текста, нажатия клавиш

  • Кнопки, панели, поле ввода текста с поддержкой кириллицы

  • Шрифты через SDL_ttf (DejaVuSans, поддержка кириллицы)

  • Отрисовка BMP‑картинок

  • удалить_символ() корректно удаляет один символ, а не байт — важно для UTF-8 (кириллица — 2 байта на символ)

Проверено: через xdotool под виртуальным дисплеем отправлены клики мыши — программа поймала оба, координаты совпали байт‑в‑байт.

Все имена функций — кириллические: инициализировать, создать_окно, создать_холст, задать_цвет, опросить_событие, выделить_память.

SQLite через FFI

база_данных.код — обёртка над SQLite через FFI. Параметризованные запросы, подготовленные выражения, привязка параметров через ?:

подключить база_данных;
результат<текст> бд_результат = открыть_базу("пример.db");
текст бд = бд_результат.значение;
выполнить_sql(бд, "CREATE TABLE IF NOT EXISTS пользователи (id INTEGER PRIMARY KEY, имя TEXT);");
// параметризованный запрос — безопасно, без инъекции

Стратегическая цель — собственное хранилище, без внешних зависимостей (см. ниже). SQLite — временное, стартовое решение.

Собственное хранилище данных

хранилище.код — полностью написано на Сфере, без единой внешней библиотеки. Формат — append‑only лог (как в Bitcask): каждое изменение дописывается в конец файла, ничего не перезаписывается на месте.

подключить хранилище;
сохранить("данные.db", "имя", "Никита");
результат<текст> имя = получить("данные.db", "имя");
если (имя.успешно) { вывести(имя.значение); }   // "Никита"
  • Поиск сканирует файл целиком (O(n)) — простое первое решение

  • Индекс в памяти — ускоряет повторные запросы

  • Компактификация (сжать_хранилище) — пересканирует файл, оставляет только свежие версии ключей, удаляет tombstone‑записи

Проведён честный аудит памяти всего проекта под valgrind: обнаружена одна маленькая (22 байта / 2 блока) утечка именно в сжать_хранилище, сознательно оставленная неисправленной — любая ручная попытка её устранить провоцировала настоящий double‑free из‑за скрытой эвристики автоматического освобождения памяти в компиляторе. Задокументировано честно, а не тихо исправлено «на глаз».

Не полноценная реляционная база — нет SQL, индексов, транзакций. Честное, стартовое решение.

Отслеживание: логи, метрики, трейсинг

Система наблюдаемости языка — три модуля.

Логи (лог.код) — уровни (отладка/инфо/предупреждение/ошибка/критическая), вывод в консоль и/или файл, метка времени (собственная реализация без вызова strftime/localtime, тем же алгоритмом, что и в <chrono> C++20):

подключить лог;
Логгер логгер = создать_логгер(уровень_инфо(), да, "приложение.log");
лог_ошибка(логгер, "не удалось подключиться к базе данных");
// [2026-09-29 19:42:46] ОШИБКА: не удалось подключиться к базе данных

Метрики (метрики.код) — счётчики и измерители, модель как в Prometheus/StatsD:

подключить метрики;
Метрики м = создать_метрики();
счётчик_увеличить_на_единицу(м, "запросы_всего");
измеритель_установить(м, "открытых_соединений", 5.0);
метрики_напечатать(м);

Трейсинг (трейсинг.код) — именованные «спаны» с вложенностью, для измерения длительности участков кода:

подключить трейсинг;
спан_начать(т, "обработать_запрос");
спан_начать(т, "прочитать_бд");
спан_завершить(т);
спан_завершить(т);

Честно, узко (v1): время всегда UTC, без часовых поясов; поддержаны только даты после 1970 года; нет структурированных полей и вывода в JSON — только человекочитаемая строка.

Стандартная библиотека

Текст

Функция

Описание

длина(текст)

Длина строки в байтах

подстрока(текст, начало, длина)

Часть строки → результат<текст>

содержит(текст, подстрока)

Проверка вхождения → верно

найти(текст, подстрока)

Позиция вхождения → результат<целое>

регистр_в_верхний(текст)

В верхний регистр

регистр_в_нижний(текст)

В нижний регистр

текст_в_целое(текст)

Разбор числа → результат<целое>

Преобразование в текст

Функция

Описание

целое_в_текст(целое)

Число → текст

дробное_в_текст(дробное)

Дробное → текст

верно_в_текст(верно)

да/нет → текст

Математика

Функция

Описание

абс(число)

Модуль числа

мин(а, б)

Меньшее из двух

макс(а, б)

Большее из двух

Ввод‑вывод

Функция

Описание

прочитать_строку()

Чтение строки из stdin

прочитать_файл(текст)

Чтение файла → результат<текст>

записать_файл(текст, текст)

Запись в файл → результат<целое>

выполнить_команду(текст)

Выполнение команды ОС → результат<целое>

размер_файла(текст)

Размер файла в байтах

прочитать_часть_файла(текст, целое, целое)

Чтение части файла

переименовать_файл(старый, новый)

Переименование файла

аргументы_командной_строки()

Аргументы CLI → Список<текст>

Низкоуровневые

Функция

Описание

прочитать_целое32(буфер, смещение)

Чтение 4 байт из буфера

записать_целое32(буфер, смещение, значение)

Запись 4 байт в буфер

прочитать_текст_по_смещению(буфер, смещение)

Чтение строки по адресу

байт_текста(текст, индекс)

Чтение одного байта (0–255)

прочитать_указатель(буфер, смещение)

Чтение 8-байтового указателя

Сайт на самом себе

В репозитории есть сайт/ — первый сайт языка, работающий на самой Сфере. HTTP‑сервер + хранилище данных, три страницы, CSS, счётчик посещений. Никакой внешней веб‑инфраструктуры — только код на Сфере.

Первая программа, подключающая несколько модулей стандартной библиотеки одновременно (сеть.код + хранилище.код). По пути обнаружен и исправлен конфликт повторных объявлений FFI‑функций — создан общий модуль память.код.

Инструменты разработчика

Помимо самого компилятора и пакетного менеджера, есть полноценный языковой сервер (LSP) для VS Code — диагностика ошибок, автодополнение, переход к определению — переиспользующий тот же лексер/парсер/семантику, что и сам компилятор (не отдельная, рассинхронизирующаяся копия правил языка), а также готовое расширение подсветки синтаксиса (sfera-vscode).

Известные ограничения

Честный список — что пока не работает или работает не полностью:

  • Каналы: только целые/указатели/текст, одноразовая передача, без закрытия (.освободить() есть, но неполученный текст на момент освобождения всё равно утечёт)

  • параллельно детерминировано: нет настоящей параллельности между потоками партии (строго по очереди); для каждого не точка передачи хода; барьер регистрации партии — только для «плоской» последовательности в одном блоке; максимум 256 потоков на партию

  • Многопараметрические generic‑структуры (Пара<А, Б>) — методы шаблона типизируются сразу для всех инстанциаций: метод, годный только для части конкретных типов, провалит компиляцию, даже если он не вызывается на «плохой» инстанциации (то же ограничение уже было и у Коробка<Т> с одним параметром)

  • Опциональный тип (тип?) — только скалярные типы (T не может быть текстом/структурой), работает только для локальных переменных (не параметр, не поле, не тип возврата), нет ?‑оператора автораспространения

  • Массив массивов (тип[N][M]) — только скалярные элементы, Точка[2][2] не поддержан

  • Нет DNS‑резолвинга и URL‑декодирования для сети

  • Отладчик не показывает значения переменных по имени (только сырые байты) — нужен полноценный DWARF‑тип для каждого типа языка, отдельная крупная задача

  • Пакетный менеджер: разрешение версий работает только в формате X.Y.Z (без пререлизов и диапазонов >=/<=/hyphen из npm) и только для пакетов с одного git‑адреса; публичный индекс — пока только git‑репозиторий со списком, не полноценный веб‑сервис (осознанно временное решение)

  • Windows‑установщик (установить-windows.ps1) написан, но не проверен вживую на реальной машине (разработка идёт без доступа к Windows); то же — для сборки самого компилятора под Android ВМЕСТЕ с интерфейсом панели‑приложения (кросс‑компиляция обычных программ под Android уже проверена на устройстве, см. выше)

  • Логи/метрики/трейсинг: время только UTC, нет структурированных полей и JSON‑вывода

Дорожная карта развития

Ближайшее

Задача

Что даёт

Сложность

Ленивая мономорфизация методов generic‑структур (по факту вызова)

Меньше ложных ошибок компиляции

Высокая

Каналы: структуры, закрытие

Полноценная многопоточность

Высокая

для каждого как точка передачи в эстафете

Полнее покрытый детерминированный планировщик

Средняя

Барьер регистрации партии вне «плоских» блоков (внутри если/циклов)

Надёжнее детерминизм в динамических сценариях

Высокая

DNS‑резолвинг

Реальная сетевая работа

Средняя

URL‑декодирование

Корректный HTTP‑сервер

Низкая

Linux‑установщик

Доступ для разработчиков

Низкая

Дальше

Задача

Что даёт

Сложность

Парсер CLI‑флагов

CLI‑приложения

Средняя

Просмотр переменных в отладчике по имени

Производственная разработка

Высокая

Вывод типа обобщённых функций

Удобство generics

Средняя

Полноценный веб‑сервис для реестра пакетов

Настоящая экосистема, как crates.io/npm

Высокая (нужен домен и сервер)

Структурированные логи (JSON, часовые пояса)

Продакшн‑наблюдаемость

Средняя

Тестирование на Эльбрус/Байкал

Господдержка

Высокая

Telegram‑канал

Обратная связь

—

Итог

«Сфера» — не игрушка и не академический эксперимент. Это рабочий компилятор с LLVM‑бэкендом, типизацией, ARC, обобщёнными типами, контрактами, многопоточностью (включая опциональный детерминированный планировщик), FFI, сетью, графикой, собственным хранилищем данных, логами/метриками/трейсингом, отладчиком с полноценным DWARF и своим пакетным менеджером с публичным индексом. Язык, на котором написан собственный сайт (скоро будет со своим доменом в публичном доступе).

Ограничения честно задокументированы — ARC не покрывает упаковку в контракт, каналы ограничены, версии пакетов пока не разрешаются автоматически. Но фундамент серьёзный, и темп развития — впечатляющий.

Если интересно — репозиторий: github.com/nikitosklim/Sfera. Лицензия MIT.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.