Здесь не будет песен и сказок о том какой крутой и недооценённый язык Forth :) По совокупности обстоятельств он скорее уже история. Однако некоторые связанные с ним моменты поучительны и перспективны - и хочется их записать на память, да и поделиться.
Историческая справка

Насчет Спутника - это не шутка. Язык в некоторой степени обязан своим появлением советскому ПС-1 (а значит и многим людям во главе с Королёвым). Автор языка, Чарльз "Чак" Мор (или Мур?) именно с него начинает статью об истории языка:
В октябре 1957 случился Спутник-1 - потрясающее время! Я был второкурсником в MIT и устроился на подработку в SAO (Смитсоновская Астрофизическая Обсерватория) в Гарварде. SAO отвечала за отслеживание спутников. Спутник-1 застал их врасплох и они стали нанимать студентов для вычислений... [коллега] рассказал мне о IBM EDPM 704 в MIT и одолжил своё руководство по Фортрану‑II. Моя первая программа Ephemeris-4 выполняла за меня мою работу.
Так я стал программистом... моя «подработка» занимала не менее 40 часов в неделю, а оценки в ВУЗе посыпались к чертям...
Работа программиста в 50е годы была суровее чем теперь. Моя программа занимала 2 лотка перфокарт, которые мне приходилось таскать с собой [одна перфокарта обычно использовалась для записи одной строки кода] - и собственноручно прогружать их в машину... Компиляция занимала минут 30, но поскольку компьютерное время было ограниченным, у меня получался обычно 1 запуск в день.
Не буду пытаться пересказать всю статью. В общем, суть в том что при небольших изменениях в вычислениях приходилось менять часть программы и следовательно перекомпилировать её. Чтобы сэкономить время автор стал выносить описание вычислительных шагов в текстовый файл — так что можно было поменять только его, а программа просто выполняла указанные шаги в новом порядке или с другими параметрами.
Сменив за десяток лет несколько мест работы, автор применял этот же подход обычно и в других проектах — понемногу из его «синтаксиса» для интерпретируемого файла вырастал некий самостоятельный язык программирования.
С какого‑то момента он стал рассматривать своё поделие именно как достаточно универсальный язык и в результате довёл его до состояния когда при переносе на другую архитектуру можно было переделать только описания базовых «слов».
В этом смысле получился язык имеющий в общем‑то ту же цель что семейка от BCPL до C — но значительно отличающийся синтаксисом, а значит парсингом — и довольно необычный в смысле выполнения тоже.
В отличие от C он не компилировался в исполнимый файл — а в отличие от интерпретаторов типа BASIC он был значительно быстрее (в зависимости от реализации - в десятки и сотни раз) - потому что всё-таки, гм, компилировался :-) При этом умещался в машины с жалкими несколькими килобайтами памяти.
Сейчас мы посмотрим-вспомним как это было устроено — и чем это актуально «в современности».
Краткий обзор FORTH
Чтобы объяснить чем интересна внутренняя реализция FORTH вспомним, как он выглядел внешне. Наверняка многие это знают и помнят - извините заранее :-)

Характерным был синтаксис — программа выглядела как последовательность «слов», каждое из которых производит некую операцию с «операционным» стеком — хранилищем, куда данные можно класть по очереди, а потом снимать в обратном порядке — вроде стопки тарелок.
Как помним, в большинстве компьютерных архитектур есть один стек — он используется для сохранения адресов возврата при входе в подпрограммы. Некоторые ранние языки и системы не имели такой возможности — поэтому «молодые» версии Фортрана, например, сохраняют адрес возврата в специальной (скрытой) переменной внутри самой подпрограммы — и больше одного раза войти в подпрограмму нельзя — для возврата только одна ячейка. Многие архитектуры позволяют на этот же стек складывать и произвольные значения — например чтобы кратковременно сохранить какие‑то значения. Со времён C через стек же передают параметры в подпрограммы — правда нужно следить чтобы он оставался правильно выровненным, иначе забыв вытолкнуть сохранённые данные мы сломаем всю цепочку адресов возвратов, хранящуюся вместе с ними.

Ну а FORTH использует два стека. Один для адресов возврата (в основном), а другой именно для работы с данными. Поэтому выражения выглядели так:
3 5 * .
в этом выражении 4 «слова» разделённых пробелами: те что в виде чисел — просто заталкивают соответсвующее значение в стек; звёздочка вытаскивает пару чисел из стека, перемножает и кладёт результат обратно (похоже действуют и другие арифметические слова); «точка» выталкивает значение из стека и печатает его. Таким образом это аналог print(3*5)
. Слова бывали и более «вербальными» — вот другой пример:
KEY 1 + EMIT
Слово KEY ожидает нажатия клавиши пользователем и заталкивает в стек код введённого символа. Единица запихнёт в стек ещё и значение 1 — после чего слово «плюс» вытолкнет их, сложит и поместит результат обратно в стек — это будет код введённого символа увеличенный на 1, то есть код следующего (по алфавиту) символа. Наконец слово EMIT выталкивает число из стека и печатает символ с соответствующим кодом. То есть у нас тут «шифрование» по способу Цезаря — осталось только в цикл завернуть.

Можно было и определять собственные слова, конечно. Синтаксис выглядел так:
: STAR 42 EMIT ;
: TRI-STAR STAR STAR STAR ;
Здесь два определения — каждое начинается со слова «двоеточие» и заканчивается словом «точка‑с-запятой». Автор в упомянутой исторической статье поясняет почему так вышло — эти двоеточия перед определяемым словом — аналоги меток (например в языке Algol и более поздних) — только для упрощения парсинга они идут перед словом а не после.
Итак, за двоеточием следует определяемое (новое) слово — а дальше тупо последовательность действий — то есть последовательность других слов. Фактически мы видим в этом «определении слов» определение пользовательских подпрограмм. FORTH в этом смысле строго «структурный» язык.
Как видите, синтаксис до безобразия простой — просто «слова» и пробелы между ними. Это проще чем LISP (там кроме пробелов ещё и скобки).
Работает FORTH в режиме интерпретатора — если пользователь вводит простые выражения — они тут же выполняются. Если вводит описания слов — они «компилируются» — добавляются в словарь (и в код). Смысл этой «компиляции» мы сейчас и рассмотрим.
Как выполняется FORTH
Простота синтаксиса ведёт к важному последствию (кроме простоты парсинга).
Что происходит когда пользователь определяет новое слово? Как оно сохраняется?
Из‑за простоты синтаксиса нам нет нужды хранить его исходным текстом. Можно сразу скомпилировать либо в коротенький интерпретируемый код, либо даже прямо в машинный.
Почему это так просто? Потому что определение состоит из цепочки слов — а каждое слово это вызов другого слова (т. е. некоей подпрограммы). Параметров у этих подпрограмм нет (они оперируют стеком данных) — поэтому нужно просто записать список адресов этих подпрограмм!
Точнее говоря, можно рассмотреть всё‑таки два типа слов — слова в виде чисел, которые вызывают добавление значени на стек — и слова в виде вызовов других слов.
Само слово (его «имя») добавляется в некий глобальный «словарь», например просто список — в этом словаре для каждого слова хранится его адрес или сама последовательность команд, если код хранится вместе со словарём (возможны разные реализации):

Таким образом «скомпилированный» код будет состоят из операций только трёх типов:
добавить число в стек
вызвать слово (подпрограмму) с таким‑то адресом
выйти из подпрограммы
То есть «скомпилированный» код может выглядеть в условных инструкциях так:
CALL "KEY"
PUSH 1
CALL "ADD"
CALL "EMIT"
RETURN
Мы немножко упрощаем — есть ведь ещё слова которые организуют ветвление и циклы — но они не много добавляют к этой картине — нужны ещё инструкции для условного и безусловного перехода например, и всё.
Чем хорош такой подход?
Хорош он тем, что даже если этот код (по‑английски его называют threaded code — в русском я встречал перевод «шитый код») не является машинным, а интерпретируется, он всё равно работает довольно быстро — не нужно ничего парсить, знай себе выполняй вызовы один за другим и всё.
Но и в машинный вариант его превратить очень легко! Действительно, если у нас есть лишь 5–7 типов команд, то не составит труда сделать генератор машинного кода который будет вставлять соответствующие 5–7 инструкций модифицируя их параметры.
Например вызов подпрограммы в 8086 архитектуре это код в духе 0xBExxxx
где четыре икса означают 4-значное (двухбайтовое) шестнадцатеричное число — адрес подпрограммы. Понятно что нам не составит труда сгенерировать это BE и приписать к нему адрес.
Так же и с операциями занесения чисел в стек — например, если стек данных организован на регистре BP, то в простейшей реализации нам нужно две команды:
MOV [BP], 0x1234 ; код С746003412
ADD BP, 2 ; код 83C502
из которых поменять нужно только саму константу — два последних байта в первой инструкции. В этом варианте, впрочем, на помещение константы в стек уходит 8 байт, можно сделать и короче. Можно использовать инструкции 80386, но они длиннее сами по себе.
Ну и поскольку FORTH в принципе несложен (сравнительно с многими другими языками) в реализации, воплощений в том числе с нативной кодогенерацией было сделано немало. Нередко даже программисты создавали реализации под конкретную задачу (например, так поступили разработчики игры Starflight). Это немало способствовало возникновению популярного мнения что язык «быстрее Си» или даже «быстрее Ассемблера» — хотя понятно что к таким замечаниям следует относиться осторожно:)
О производительности
Бесспорно что FORTH позволял создавать программы очень компактные — впрочем максимальная компактность скомпилированного кода необязательно вела к максимальному быстродействию. Что стоит за мнением о «скорости» этого языка?
Во‑первых сравнение с современными ему интерпретаторами на машинах со схожими скромными возможностями. Мы теперь понимаем почему так — он не парсит строки исходного текста по мере исполнения а готовит довольно оптимизированный код (интерпретируемый или нативный) заранее.
Во‑вторых он предлагает особенную Calling Convention — способ передачи параметров в подпрограммы и обратно — за счет своего стека данных.
Действительно, привычная calling convention из Си — с ней значения хранятся в переменных в памяти (в основном) и перед вызовом запихиваются в стек вызовов, откуда подпрограмма их уже использует как локально адресуемые переменные. Вроде бы почти то же самое — но вот для возврата значений такой механики нет. Кроме того если есть вложенный вызов, и в нижележащую функцию надо передать значение «полученное свыше» — его придётся ещё раз пихнуть в стек. В общем, неоптимально.
При программировании на ассемблере мы можем вместо этого передавать параметры в регистрах. В идеальном случае это будет быстрее. К сожалению с одной стороны часто трудно угадать идеальный вариант и мы сохраняем сами регистры на стеке чтобы случайно их не испортить — с другой проблема вложенных вызовов и тут не решена.
FORTH предлагает непохожую на эти две крайности концепцию — мы используем стек, но живущий независимо от стека вызовов. Можно и возвращать значения на нём же. И при вложенных вызовах данные могут просто спокойно лежать там же где лежали.
Интересно что этот подход в принципе можно использовать и в Си и в Ассемблере — кто мешает отдельный стек создать.
Поэтому хотя строго говоря ясно что идеальную программу на этих языках можно написать уж точно не хуже чем на FORTH — но в реальной ситуации часто код на них не будет идеален, из‑за чего и возникают «чрезмерно оптимистичные» впечатления от FORTH.
Стековые Виртуальные Машины
Но это мы отвлеклись. Хвалить FORTH — не наша цель. Вместе с достоинствами в нём сложилось и достаточно особенностей которые не дали ему удержать даже ту популярность которая у него была. Сейчас он скорее архаизм.
Но те ключевые моменты на которые мы обратили внимание — они получили очень широкое распространение. Действительно — если кому‑то захочется изобрести новый язык программирования — выглядит очень привлекательно сделать так чтобы он транслировался в памяти в «цепочки слов» наподобие FORTH — и исполнялся в некоей виртуальной стековой машине.
И действительно — если мы посмотрим на более популярные языки — например Java, Python (и PyPy), Lua — а с ними же и несколько более узкоспециализированные WebAssembly, Ethereum EVM, TON TVM из тех что болше на слуху — оказывается что внутри они используют именно стековую машину. Кроме определенного удобства при трансляции это облегчает и внедрение JIT‑компилятора. Фактически, FORTH компилирующий слова в цепочки нативного threaded code в памяти — это тоже JIT‑компиляция!
Мы не говорим о том что стек для данных или threaded code был придуман вместе с FORTH (скорее эти концепции появились паралельно ему и были удачно подхвачены) — но безусловно он сыграл значительную роль в популяризации этого подхода.
И это мы себе намотаем на ус на случай если тоже соберемся изобретат языки и компиляторы:)
Заключение
В обзоре FORTH дело представлено так будто это совсем простой язык — тут следует оговориться, он прост, да не прост :-) под капотом у него создана остроумная идея разных типов слов — одни работают только в определениях других слов — другие наоборот только при интерпретации — и т. п. — и это позволяет использовать его как «мета‑язык» (авторское название) для самоописания и саморасширения. Эту очень интересную тему мы совершенно игнорируем в данной статье т.к. все таки рассказ был о другом — не столько о FORTH, сколько о «пути» в разработке языков, который он нам «указывает». Тем не менее если Вы забавы ради захотите подробнее познакомиться с этим по‑своему культовым языком — вы, пожалуй, не пожалеете :-)
Желающим осмелюсь порекомендовать книжки Лео Броуди. К сожалению, кажется, идеальной литературы по FORTH не сложилось (отчасти из‑за его необычности и обманчивой простоты) — но это вероятно лучшее что есть. Популярная Starting Forth опубликована официально в интернете: https://www.forth.com/starting‑forth/ — из этой книжки позаимствованы и некоторые забавные иллюстрации использованные в данной статье.
Комментарии (11)
kt97679
21.08.2025 05:25Вот здесь много интересных людей общается: https://github.com/ForthHub/discussion/discussions
RodionGork Автор
21.08.2025 05:25спасибо :) кажется я впервые узнал что в качестве форума можно гитхаб использовать!
Denis_Chernyshev
21.08.2025 05:25Ох, книги Броуди, "какая боль". Комикс для чайников, и материал поданный задом наперед. А главное в форт-системе это же не синтаксис, а организация динамической памяти структурой словаря. И синтаксис - это системное средство для работы со словарем.
Если рискнете читать Броуди, то держите постоянно в голове: написано по старому стандарту Forth-83, и есть серьезные отличия от ANSI Forth-95 (современный неофициальный Forth уже не ломает ANSI-95, а только дополняет).
RodionGork Автор
21.08.2025 05:25Ох, книги Броуди, "какая боль". Комикс для чайников, и материал поданный задом наперед.
яростно поддержу :) мне он всегда казался излишне вербозным - и да, то что в общем-то самое интересное там лишь очень кратко затронуто где-то в главе "under the hood"
к сожалению на русском видел ещё только одну (Келли, Спайс) и прямо скажем тоже не готов её порекомендовать - там и Forth-79 с Forth-83 спорит постоянно и много материала который сейчас уж точно даже историческую ценность едва ли представляет.
было бы интересно полистать хорошую книжку как раз о реализации языка, точнее о вариантах реализации даже. но такой я не видел и на английском.
atues
21.08.2025 05:25А что именно Вы смотрели? Есть книга Баранова и Ноздрунова "Язык Форт и его реализации", есть Семенов "Программирование на языке Форт" (даже исходники на ассемблере приложены). Книги старые, конечно, но информация там вполне корректная и вполне может служить в качестве руководства, как мне кацца )))
RodionGork Автор
21.08.2025 05:25спасибо за рекомендации, этих я точно не видел, полистаю с любопытством!
atues
21.08.2025 05:25:))) Выше я давал ссылку на русскоязычный форум. Там информации вагон и маленькая тележка. Главное - найти. И народ там доброжелательный: спрашивайте - наведут на цель
atues
21.08.2025 05:25Книги Броуди, imho, и не предназначались для детального технического раскрытия того, как именно функционируют Форт-системы. Да, рассказано "по верхам", но для начинающих и это, по-большому счету, не самая нужная информация. Научить делать самые фундаментальные вещи - и этого вполне достаточно. А уж потом, когда появится навык программирования, можно сделать попытку "слепить" свою реализацию. Тут, конечно, уже нужно рыть глубже. Но точно не начинающим.
RodionGork Автор
21.08.2025 05:25ну да, судя по картинкам с разъяснением что такое стек книжка именно для широкой публики, м.б. для популяризации домашних компьютеров в которых был прошит форт вместо бейсика... правда их вроде было не очень много
atues
Отечественный и достаточно активный форум по Forth-у: https://fforum.winglion.ru/index.php
RodionGork Автор
спасибо! последнее сообщение 5 августа :) но безусловно хорошо когда есть где задать вопрос или поделиться мыслями :)
я сам по большей части реддитом пользуюсь соответствующим, тоже достаточно активен