В современных языках программирования мы почти всегда ожидаем, что рядом с языком будет удобный инструмент для работы с проектом.
В Elixir есть Mix. В Rust есть Cargo. В Ruby есть Bundler и RubyGems. В Haskell есть Cabal и Stack. В JavaScript есть npm, pnpm и Yarn. В Go есть modules.
Мы привыкли, что можно создать проект одной командой, описать зависимости в одном файле, подтянуть их из сети, запустить тесты, собрать релиз. Это кажется базовой частью экосистемы, а не отдельной роскошью.
Но у старых языков программирования часто другая история.
Когда многие из них создавались, такой подход ещё не был стандартом. Код распространялся иначе, проекты были устроены иначе, а экосистемы развивались без привычных сегодня package managers и build tools.
И это до сих пор чувствуется.
Почему я задумался об этом
Для одного из своих проектов я использую Forth.
Forth — очень интересный язык. Минималистичный, прямой, необычный. В нём есть что-то очень привлекательное: ты находишься близко к вычислениям, близко к стеку, близко к самому железу вычислительной машины (которую иногда я делаю сам).
Но когда я начал писать на Forth что-то более-менее похожее на проект, мне быстро стало не хватать привычных вещей:
- создать новый проект;
- описать зависимости;
- скачать зависимости;
- запускать тесты;
- иметь понятную структуру проекта;
- подключать библиотеки из Git;
- не держать всё вручную в голове.
Да, можно использовать git submodules, можно копировать файлы руками, можно придумывать свои соглашения. Но для меня это не очень удобный путь. Я хотел что-то ближе к тому, к чему привык в современных языках.
Я нашёл библиотеку f.4th, связанную с theforth.net. Это полезный инструмент, но у него другой подход: библиотеки и версии хранятся на своём сервере.
Мне же хотелось уметь подтягивать зависимости прямо из Git-репозиториев. Например, указать GitHub URL, ветку или тег, и получить библиотеку в проект.
Так появилась идея сделать fmix.
Что такое FMix
fmix — это небольшой build tool и package tool для Forth, вдохновлённый Elixir Mix.
Репозиторий: github.com/VitaSound/fmix
Он не пытается быть большим фреймворком. Его задача проще: дать Forth-проекту минимальный набор привычных инструментов.
Сейчас fmix умеет:
fmix new example
fmix packages.get
fmix test
fmix version
То есть можно создать новый пакет, подтянуть зависимости, запустить тесты и посмотреть версию инструмента.
Создание проекта
Новый проект создаётся так:
fmix new example
cd example
После этого появляется базовая структура проекта:
example/
example.4th
package.4th
README.md
tests/
forth-packages/
package.4th — это файл описания пакета. В нём указывается имя, версия, лицензия, основной файл и зависимости.
Пример:
forth-package
key-value name example
key-value version 0.1.0
key-value license COPL
key-value description example
key-value main example.4th
key-list dependencies f git https://github.com/VitaSound/f tag 0.2.4
key-list dependencies ttester git https://github.com/VitaSound/ttester tag 1.1.0
end-forth-package
Мне нравится, что сам файл тоже написан в стиле Forth. Это не JSON, не YAML и не TOML, а маленький декларативный Forth-формат.
Зависимости из Git
Главная причина, по которой я начал делать fmix, — это зависимости из Git.
В package.4th можно указать зависимость так:
key-list dependencies ftest git https://github.com/VitaSound/ftest.git branch main
Или так, если нужна конкретная версия через tag:
key-list dependencies ftest git https://github.com/VitaSound/ftest.git tag 0.1.0
После этого достаточно выполнить:
fmix packages.get
Зависимости будут установлены в локальную папку проекта:
./forth-packages/
Структура позволяет хранить разные версии одной библиотеки рядом друг с другом:
forth-packages/
f/
0.2.4/
ttester/
1.1.0/
Подключать зависимость можно обычным require:
require ./forth-packages/f/0.2.4/f.4th
Поддержка theforth.net
Хотя мне хотелось Git-зависимостей, я не хотел полностью отказываться от существующей экосистемы.
Поэтому fmix также умеет работать с пакетами из theforth.net через f.4th.
Например:
key-list dependencies f 0.2.4
Таким образом, в одном проекте можно использовать и Git-зависимости, и пакеты из theforth.net.
Тесты
Ещё одна базовая вещь, которой мне не хватало, — запуск тестов.
В fmix есть команда:
fmix test
Она запускает тесты из директории tests/.
Можно запустить и конкретный файл:
fmix test tests/some_test.4th
Для тестов используется ttester. Если ttester есть в зависимостях проекта, fmix берёт его из ./forth-packages. Если нет — использует версию из установленного FMIX_HOME.
Для меня это важно: тесты должны запускаться одной командой. Даже если проект маленький.
Что было исправлено в последних версиях
fmix пережил уже несколько поколений изменений.
Первые версии я писал сам. Но я не очень сильный Forth-разработчик, поэтому многие вещи давались тяжело. Одну крупную переработку я сделал с помощью Gemini. Последнюю версию я уже доводил с помощью Cursor.
В последних релизах было исправлено довольно много практических проблем.
Появился GitHub Actions CI: workflow собирает GForth 0.7.9 из исходников и запускает:
gforth fmix.4th -e version
gforth fmix.4th -e test
Позже появился release pipeline: при push version tag автоматически создаётся GitHub Release, а текст релиза берётся из файла .github/RELEASE_NOTES_X.Y.Z.md.
Также были исправлены проблемы с путями. Раньше часть логики была завязана на PWD, теперь проект определяется через текущую директорию запуска. Это важно, потому что FMIX_HOME указывает на установленный fmix, а команды должны работать с текущим проектом.
Были исправлены ошибки при установке зависимостей:
-
git cloneтеперь проверяет результат; - неуспешные shell-команды завершают GForth с exit code
1; - перед
git cloneсоздаются родительские директории; -
git fetchиcheckoutвыполняются так, чтобы ошибка не маскировалась; - для Git-команд используется
GIT_TERMINAL_PROMPT=0, чтобы команда не зависала в non-interactive окружении.
Ещё одна важная вещь — валидация входных данных. fmix проверяет имена пакетов, версии, пути, Git URL и Git refs перед тем, как вызывать cp, sed или git. Это не сложная система escaping, а простой whitelist, но он отсекает пробелы, ;, $, backticks, pipes и другие shell metacharacters.
Неожиданная проблема: терминал
Отдельная история была с терминалом. Перед тем, как пользоваться командой fmix в консоли, ее нужно прописать как alias.
Так вот при запуске через простой alias вроде:
alias fmix='gforth "$FMIX_HOME/fmix.4th" -e'
после выполнения команд терминал иногда оставался в плохом состоянии. На prompt появлялись странные символы вроде:
0c0c
[?2004l
Особенно это проявлялось в WSL.
В версии 0.4.4 я перешёл на launcher script bin/fmix. Он восстанавливает TTY после команд, сбрасывает bracketed paste mode и немного очищает накопившийся ввод из /dev/tty. На самом деле все эти вещи я бы точно не разрешил самостоятельно. Я терпеливо помогал нейросети раз за разом пробовать некоторые гипотезы, чтобы исправить эту проблему. В отличие от тех ИИ, что я пробовал раньше, в этот раз я только один раз решил прервать ИИ и создать новый контекст, потому что она как будто зациклилась в бесконечном исправлении.
Однако, получилось и теперь вместо alias рекомендуется использовать:
export FMIX_HOME="$HOME/fmix"
export PATH="$FMIX_HOME/bin:$PATH"
И запускать просто:
fmix
Установка
Сейчас установка выглядит так:
git clone https://github.com/VitaSound/fmix.git ~/fmix
Затем нужно добавить в shell config (например .bashrc):
export FMIX_HOME="$HOME/fmix"
export PATH="$FMIX_HOME/bin:$PATH"
После этого команда начнет работать прямо в консоли:
fmix version
Важно: сейчас fmix рассчитан на Linux, Git, sed, cp и GForth 0.7.9.
Я не рекомендую использовать GForth более ранних версий и версию из Snap. Snap запускает программы в изолированном окружении, из-за чего текущая директория и пути могут не совпадать с тем, что ожидает shell session. Это ломает команды вроде new и packages.get.
Лучше использовать GForth из apt, локальную сборку или tarball с официальной страницы GForth.
Зачем это всё
Мне кажется, что старым языкам очень нужны такие инструменты, чтобы соответсвовать современным реалиям.
Не обязательно огромные. Не обязательно идеальные. Но хотя бы такие, которые закрывают базовые сценарии. Когда у языка есть удобный инструмент вокруг проектов, он становится дружелюбнее.
Новому человеку проще попробовать язык. Опытному пользователю проще начать новый проект. Библиотеки проще переиспользовать. Тесты проще запускать.
Это не меняет сам язык, но меняет опыт работы с ним. Позволяет взять старый инструмент в новые проекты.
В случае Forth это особенно интересно. Forth часто воспринимается как что-то старое, странное и нишевое. Но, возможно, часть этой «старости» находится не только в языке, а в отсутствии привычной современной обвязки.
Если добавить удобные инструменты, язык может ощущаться совсем иначе.
Что дальше
fmix пока маленький и экспериментальный.
Сейчас он умеет создавать пакеты, подтягивать зависимости из Git и theforth.net, запускать тесты.
В будущем хочется улучшать совместимость, добавить документацию, и, возможно, добавить больше команд вокруг разработки Forth-пакетов.
Но уже сейчас мне стало гораздо удобнее работать над своими Forth-проектами. Но это уже совсем другая история




Top comments (0)