Forth — язык со стековой моделью вычислений, который появился в 1970-х и до сих пор применяется в прошивках, загрузчиках и системах, где важны размер и предсказуемость. В нём нет привычного синтаксиса выражений: операнды кладутся на стек, а слова забирают их и возвращают результат.
Статья поможет пройти путь от первого выражения в Gforth до собственных слов, работы с памятью и понимания того, как Forth устроен внутри. Примеры рассчитаны на Linux; для микроконтроллеров принципы те же, но среда другая.
Модель стека и обратная польская запись
Выражение 2 3 + означает: положить 2, положить 3, выполнить сложение. Результат 5 остаётся на стеке. Точка . печатает верхнее число и снимает его. Такой порядок называют обратной польской записью; он избавляет от скобок и приоритетов.
Стек — не деталь реализации, а главный интерфейс. Слово описывают стек-эффектом в комментарии: ( n1 n2 -- n3 ). Это дисциплина, которая заменяет сигнатуры типов и очень помогает при чтении чужого кода.
sudo apt install gforth
gforth -e '2 3 + . cr bye'
gforth -e '10 3 /mod . . cr bye'
gforth -e '1 2 3 .s cr bye'
Слово /mod оставляет остаток и частное. .s показывает содержимое стека, не меняя его. Эти два инструмента стоит освоить первыми: они дают обратную связь без отладчика.
Определение слов
Слово определяется двоеточием и точкой с запятой. Всё между ними компилируется в словарь. Имя может содержать почти любые символы, кроме пробелов; регистр обычно не важен, но в исходниках придерживаются нижнего регистра.
: square ( n -- n*n ) dup * ;
: cube ( n -- n^3 ) dup square * ;
: average ( a b -- avg ) + 2 / ;
7 square . cr
3 4 average . cr
dup дублирует верхнее значение, * перемножает два верхних. В average сначала складываем, затем делим на два. Проверка стек-эффекта помогает заметить ошибку до запуска: если слово забирает лишнее, программа «поедет».
Комментарии бывают двух видов: \ до конца строки и ( ... ) до закрывающей скобки. Второй вариант требует пробела после открывающей скобки — это слово, а не синтаксис.
Слова для работы со стеком
Большинство ошибок в Forth — неверный порядок или количество элементов. Набор операций небольшой, но им нужно пользоваться осознанно.
1 2 3 .s
swap .s
rot .s
nip .s
2drop .s
depth . cr
dup— продублировать верхний элемент.drop— удалить верхний.swap— поменять два верхних местами.over— скопировать второй элемент наверх.rot— повернуть три элемента.nip— удалить второй сверху.tuck— вставить копию верхнего под второй.
Для отладки полезны .s, depth и dump. Если после слова на стеке остался мусор, ищите нарушенный стек-эффект. Встроенного сборщика мусора нет, поэтому за балансом следите сами.
Управление потоком
Условные конструкции компилируются в слова if, else, then. Флаг — это число: ноль означает ложь, любое ненулевое значение — истину. Циклы бывают с выходом по условию и с счётчиком.
: abs ( n -- |n| ) dup 0< if negate then ;
: countdown ( n -- ) begin dup . 1- dup 0= until drop ;
: table ( n -- ) 1+ 1 do i . loop ;
5 countdown cr
5 table cr
begin ... until повторяет тело, пока флаг равен нулю. do ... loop использует счётчик i; ?do пропускает цикл при нулевом диапазоне. Для выхода из середины есть leave и unloop.
Рекурсия задаётся словом recurse, а не именем: имя внутри определения ещё может быть не завершено. Классический пример — факториал:
: fact ( n -- n! ) dup 1 > if dup 1- recurse * then ;
6 fact . cr
Память, переменные и массивы
Forth даёт прямой доступ к памяти. variable создаёт ячейку, @ читает, ! записывает. Адрес возвращается на стек, поэтому операции выглядят как counter @ и 42 counter !.
variable counter
0 counter !
counter @ . cr
5 counter +!
counter @ . cr
Массивы создают через create и allot. Размер умножают на cells, потому что ячейка зависит от разрядности платформы. Индекс не проверяется: выход за границы — классическая причина падения прошивки.
create squares 10 cells allot
: fill ( -- ) 10 0 do i dup * squares i cells + ! loop ;
: show ( -- ) 10 0 do squares i cells + @ . loop ;
fill show cr
Слово does> позволяет строить определяющие слова: create выделяет данные, а does> задаёт поведение при обращении. На этом приёме держатся структуры, константы и таблицы переходов.
Строки, вывод и отладка
Строка в Forth — это пара «адрес, длина». s" ..." кладёт обе величины, type печатает. ." выводит строку сразу при выполнении слова.
: greet ( -- ) ." Hello, Forth" cr ;
greet
s" stack language" type cr
see square
words
see декомпилирует слово, words показывает словарь. В Gforth есть require для загрузки файлов и include для вставки исходника. Для тестов используют набор t{ ... -> ... }t: он сравнивает стек до и после.
На микроконтроллере отладка сложнее: нет файловой системы и терминала. Обычно выводят символы в UART, мигают светодиодом и проверяют стек через .s. Полезно заранее договориться о формате сообщений и не блокировать прерывания надолго.
Forth во встраиваемых системах
Компилятор Forth компактен, а исполнение предсказуемо: обычно нет динамического выделения памяти и сборщика мусора. Это ценно для Cortex-M, AVR и ESP32. Популярные среды — Mecrisp-Stellaris, amforth и esp32forth; они дают доступ к регистрам, прерываниям и периферии.
Обычно применяют кросс-компиляцию: на большом компьютере собирают образ, который затем прошивают. В самом устройстве остаётся небольшое ядро и слова предметной области. Такой подход удобен для протоколов, датчиков и управляющих автоматов.
Обратная сторона — отсутствие защиты памяти и типов. Ошибка в адресе может испортить соседнюю структуру или вектор прерывания. Поэтому код делят на маленькие слова, покрывают тестами на хосте и документируют стек-эффекты.
Практический чек-лист
- Описывайте стек-эффект каждого слова и проверяйте его через
.s. - Держите определения короткими: одно слово — одна операция.
- Не используйте «магические» адреса, выносите их в константы.
- Проверяйте границы массивов и переполнение счётчиков.
- Тестируйте логику на Gforth до прошивки устройства.
- Для отладки на железе предусмотрите UART и безопасный режим загрузки.
Итог
Forth не пытается быть универсальным языком приложений. Его сила — минимальная среда, прямой доступ к железу и понятная модель стека. Освоив dup, swap, определения и циклы, вы получаете инструмент для прошивок и экспериментов, который укладывается в килобайты. Начинайте с Gforth, переносите проверенные слова на микроконтроллер и не пренебрегайте дисциплиной стек-эффектов — тогда язык окупается компактностью и предсказуемостью.