1. Предыстория
Месяц тому назад я реализовал интерпретатор Forth на Elixir, о чем поведал на Хабре. Этот гибрид получил составное имя Forth-ibE в честь своих родителей (Forth in-build Elixir).
Следующим шагом разработки стало определение API обмена сообщениями в распределенной команде движков Forth для совместной работы.
У читателя обязательно возникнут вопросы типа зачем и почему. Поэтому сейчас необходимо описать пару дилемм, которые были разрешены в самом начале.
Во–первых, в [1] говорится, что
«наряду с однозадачными существуют и мультизадачные Форт-системы. Они могут работать с произвольным числом задач. Задача может быть либо терминальной, при выполнении которой вся интерактивная мощь Форта передается оператору, сидящему за терминалом, либо управляющей, которая обеспечивает управление аппаратным средством, не имеющим терминала.
Управляющая задача имеет пару стеков и небольшой набор пользовательских переменных. Так как при выполнении управляющей задачи не используется терминал, ей не требуются ни собственный словарь, ни рабочая область, ни буфер входного текста.»
Внешне, формально это похоже на мою задумку команды движков Forth, но понятно, что в [1] описаны движки, размещенные в памяти одного компьютера. В Elixir/Erlang процессы движков Forth получают в распоряжение виртуальные машины BEAM, а следовательно, и узлы.
«Узлы можно запускать как на одном хосте, так и на нескольких. После установления связи между узлами процессы одного узла могут взаимодействовать с процессами других узлов с помощью стандартного механизма обмена сообщениями.»[2]
Из этого выросла реальная цель — распределенное параллельное программирование на Forth.
Во–вторых, в июле прошлого года я добрался до этапа реализации движков числовой обработки в так называемых внешних программах, с которыми рабочие процессы Elixir взаимодействуют через порты Erlang. В качестве языка программирования внешних программ выбрал Python и/или Ruby, как популярные у прикладных программистов.
В тот момент в разработке наступил кризис: я узнал, что внешние программы в Elixir/Erlang имеют ограничение на своё количество. Ведь интерпретатор как внешняя программа — это полновесный процесс ОС! На моём компьютере с увеличенным объёмом ОЗУ выходило 500 процессов. А мне представлялось, что для каждого процесса Elixir, которых может быть от 100 тыс. до миллиона, необходим один экземпляр интерпретатора. Можно было бы организовать пул интерпретаторов, но совершенно не улыбалось каждый раз перезагружать код внешней программы. По крайней мере, тогда у меня были такие представления.
Было ясно, что нужен очень эффективный и экономичный интерпретатор как я нетолерантно пошутил на форуме разработчиков Elixir: «Как АК-47». Тут я вспомнил про старый добрый язык Forth и начал искать на стеллажах книжку 1990г Лео Броуди [1].
Справка: y русскоязычном сайте Forth есть карта мира [4], на которой помечаются географические точки, от куда заходили, интересующиеся Forth, и статистика обращений. Так вот, количество посещений почти 1 млн человек с 01.10.2015. Не много, и, по—моему, Forth — язык с не до конца раскрытым потенциалом.
Параллельно со чтением учебника по Forth проверил в Интернете наличие реализации интерпретатора Forth на Elixir. Таких реализаций оказалось достаточно много за рубежом, но меня они не устроили по теоретической причине, описанной в предыдущей статье https://habr.com/ru/articles/985894/.
После разработки «терминального» «однозадачного» интерпретатора Forth я приступил к вложению интерпретатора в API–оболочку GenServer OTP. Чтобы не прерывать постоянно идущий процесс отладки, было принято решение сосредоточиться на разработке движков «нижнего уровня» — программных тактовых генераторов. Этой теме посвящена настоящая статья.
2. Начальное состояние задачи
Вернусь к задачам разработки, которые стояли, когда мы в последний раз приостановили общение [5].
Мой замысел был следующий: заменить у интерпретатора Forth терминал ввода/вывода на механизм сообщений. Начал я с получения небольшого набора функций API:
execute(words) — запуск выполнения набора words,
add_var(name, value) — установить переменную name со значением value,
get_var(name) — получить значение переменной name.
По идее достаточной только одной execute(words), но я решил добавить get_var(name) и add_var(name, value) для работы с таблицей переменных напрямую, а не через интерпретатор. По-видимому, функции get_var(name) и add_var(name, value) будут полезны пользователю для отладки.
Ещё решил не предоставлять функцию load(source) для загрузки с внешних носителей, т.к. пока не вижу в ней необходимости в режиме работы в сети акторов. Начальную загрузку узла можно производить через процессы подобные супервизорам, но я бы предпочел называть их менеджерами, т.к. их основной задачей является управление движками и потоками данных.
Замечание: в статье иногда вместо технического термина «движок» использую термин «актор», более широкий в теоретическом смысле.
3. О Времени в широком смысле слова
Как я уже сказал, на данном этапе разработки было принято решение в начале реализовать программные тактовые генераторы, т.к. они обладают всеми свойствами движков, и компьютерные системы управления не обходятся без генераторов тактовых импульсов, обеспечивающих синхронизацию потоков данных и процессов.
Пришлось углубиться в чтение статей по проблемам времени в компьютерных системах о стандартах, протоколах, шкалах времени, проблеме високосной секунды... Да-да, секунды. Для нас это мгновение, но есть области, где это величина космического масштаба. Например, в навигационных системах GPS, ГЛОНАСС, где время мерится атомными часами, а точность — наносекундами.
Чем больше я погружался в общую проблему времени, тем более отчетливо понимал, что она носит научный характер и априори существует два подхода и две среды её реш��ния:
фундаментальная или академическая и
прикладная.
Основные объекты, изучаемые академической средой:
Всемирное время (UT1), за которое отвечает Международная служба вращения Земли и систем отчета (IERS);
Международное атомное время (TAI), за которое отвечает Международное бюро мер и весов (BIPM) со штаб квартирой в Париже;
Всемирное координированное время (UTC), поддерживаемое BIPM; BIPM рассчитывает и распространяет стандартную шкалу времени UTC по миру, а национальные лаборатории определяют локальные значения UTC.
Я могу ошибаться, но когда говорят об UTC, то имеют в виду значение времени по Гринвичу, например, 2085-04-12 23:20:50.521Z — так называемое время нулевого смещения от UTC; суффикс Zulu обозначает нулевое смещение от UTC (00:00)[9]. Остальные представления являются местным временем, заданные через смещение относительно UTC. Например, местное время часового пояса Asia/Yekaterinburg запишется так: 2025-12-19 16:39:57.124+05:00.
Не стоило бы останавливаться на согласования времени на академическом уровне, если бы это не влияло на прикладной уровень. Не вдаваясь в подробности, отмечу, что в проблему согласования UT1, UTC и TAI вовлечено множество заинтересованных юридических лиц. Слишком много. И стороны пока не пришли к консенсусу в методике синхронизации UT1, UTC и TAI.
В источнике сказано [10]:
«Сегодня BIPM, МСЭ-R и другие организации разрабатывают новый порядок, который должен вступить в силу в 2035 году. Он предусматривает установку нового допустимого расхождения UT1 и UTC, чтобы UTC продолжало работу в текущих и будущих приложениях измерения времени.
Для адаптации к изменению UTC ряд пользователей, в том числе астрономическая, морская навигационная, подвижная и воздушная службы, потребовали дополнительное время на обновление систем.»
Так как глобальные проблемы Земного времени не решены, и будут ли решены — не известно, в определенные моменты мировое время оказывается рассогласованным на секунду! Что это означает для навигационных систем, я говорил выше. Но есть и другие важные отрасли, в которых могут из–за рассинхронизации времени произойти глобальные сбои, например, в телефонии.
Кроме того, синхронизация времени критична для совместной сетевой работы компьютеров [6]. Поэтому на прикладном уровне пользователи "выкручиваются" как могут. Там же в [10] описывается, что
«Google размазывает дополнительную секунду по предыдущим 24 часам, Facebook — по последующим 18 часам, Microsoft — по предыдущим двум секундам, а Alibaba — по интервалу в 24 часа, на середину которого и приходится дополнительная секунда.»
Следует так же иметь в виду, что существует самостоятельная система отметок времени Unix — способ отслеживания времени в секундах эпохи Unix, которая отсчитывается с 1 января 1970 года UTC. Эта отметка времени технически не меняется независимо от того, где пользователь находится на земном шаре! В Unix–времени високосные секунды не «размазывается», а соответствующие номера секунд просто повторяются. Вот такая получается чехарда.
Как принято у западных авторов выходить из непростой ситуации, можно сказать, есть и хорошая новость. Разработчики Erlang, имеющие корни в телекоммуникационной компании Ericcson, несколько десятилетий искали, придумывали и воплощали способы синхронизации времени процессов на виртуальных машинах BEAM. В результате у приложений на языке Erlang достигнута очень высокая надежность и далеко не последнюю роль в этом играет синхронизация по времени.
Справка. «Время имеет решающее значение для программы на Erlang, и, что еще важнее, корректное время имеет решающее значение для программы на Erlang. Поскольку Erlang — это язык с мягкими свойствами реального времени, и мы можем выражать время в наших программах, виртуальная машина и язык должны тщательно определять, что считается корректным временем и как ведут себя функции времени.» [7,8]
В результате у меня сложилось убеждение, что разработчики интегрирующих систем мировое время не особенно то и отслеживают. Достаточно удерживать синхронизацию сервисов на кластерах с заданной точностью в пределах локального времени из одного центра.
В итоге я смастерил самый простой способ синхронизации движков, а именно, в рамках всего приложения для синхронизации организовал один тактовый генератор с одно–миллисекундной точностью, который отвечает на запросы временных меток. По сути, это временные метки Unix, выравненные с монотонным временем Erlang.
В этом разделе я попытался систематизировать полученные знания о времени. Советы и критика со стороны читателей благосклонно принимается.
4. О реализация OTP GenServer тактового генератора
Генератор синхронизирующих временных меток был реализован как OTP GenServer чисто средствами Elixir/Erlang. Он настолько простой, что интерпретатор Forth не потребовался.
После реализации сервера синхронизации я обнаружил одну интересную особенность программного генератора: у OTP сервера тактового генератора отсутствует состояние. Да–да, у сервера! OTP сервер как бы одалживает состояние у другого «объекта»: в данном случае системное монотонное время Erlang
В конечном итоге я сделал заключение, что такая ситуация нормальная и закономерная. Программный сервер синхронизации, раздающий временные метки, будет всегда обертывать аппаратное устройство счета времени: атомные часы, GPS-приёмники, NTP-сервер Stratum-1, устройства типа Radio Controlled Clock или простой кварцевый генератор.
Остальные движки тоже реализуются при помощи OTP GenServer, «снаряженного» скриптами на Forth для решения прикладных подзадач в потоке данных. Думаю, что применение термина скрипт к коду на Forth не вызовет возражения, т.к. Forth как бы погружён в среду Elixir.
Прежде чем показать несколько тактовых генераторов на языке Forth, необходимо рассказать о соглашениях системы.
5. Некоторые принятые соглашения в системе
5.1. Соглашения на уровне синхронизирующего тактового генератора
Для определенности серверу тактового генератора присвоено глобальное имя :sys_timer. Запрос на получение метки синхронизации оформляется производится через API функцию:
Sys_timer.receive_time/0
Ответом тактового генератора является кортеж типа:
{:tick, <целое число>, :sys_timer}
Целое число представляет собой метку времени Unix с точностью до одной миллисекунда.
5.2. Соглашения на уровне движка
А на уровне движка в начале было слово и слово было DELAY (задержка). С этим словом интерпретатор Forth-ibE был уже реализован и почти написана настоящая статья, но потом я обнаружил, что в стандарте Forth уже есть эквивалентное слово MS — «ожидание, по крайней мере, μ миллисекунд» [11]. Мне показалось, что слово DELAY более выразительное, поэтому ничего не стал исправлять и в Forth-IbE просто добавил второе слова.
Реализовать в Forth-ibE задержку было просто через встроенную функцию в Elixir. Минимальная задержка DELAY миллисекундная. Эта задержка вставляется в пользовательское определение слова тактового цикла, для которого я применяю слово CLOCK по умолчанию. Но это как вам будет удобно.
При создании движка ему передается список движков, которые будут стоками согласно терминологии EDA [13]. Затем ему присваивается глобальное имя, которое затем прошивается в словаре интерпретатора Forth в переменной eng-name. Например, при ручном наборе в оболочке iex это будет выглядеть так:
# Запуск движка
ForthIbE.start({:e7, [self()]})
ForthIbE.add_var(:e7, "eng-name", :e7)
Последнее действие можно выполнить и с помощью Forth-ibE:
ForthIbE.execute({:e7, "VARIABLE eng-name")
ForthIbE.execute(:e7, "e7 TO-ATOM eng-name !",)
Для построения определения работы тактового цикла потребовались ввести новые встроенные слова, с которыми можно увидеть в прилагаемом дополнении к ТУ. А пока познакомлю с некоторыми шаблонами определений типовых тактовых генераторов.
6. Некоторые шаблоны определения тактового генератора в Forth-ibE
6.1. Начнем с определения простого цикла:
: CLOCK 10 0 DO tick TO-ATOM TIMESTAMP eng-name @ stocks FORWARD 500 DELAY LOOP ;
, где 1000 – ко��ичество тиков
tick – символьная строка, преобразуемая в атом, которым начинается возвращаемый кортеж
eng-name — атом названия регистрации движка
500 – величина задержки
Слово FORWARD, обозначающее пересылку кортежа отладочных данных из стека, введено в Forth-ibE для тестирования системы в целом и оказалось полезной конструкцией для прохода сквозь механизм обратных функций OTP GenServer.
Пример применение определения цикла через API движка в оболочке Elixir:
ForthIbE.start({:e1, [self()]})
ForthIbE.add_var(:e1, "eng-name", :e1)
script = ": CLOCK 10 0 DO tick TO-ATOM TIMESTAMP eng-name @ FORWARD 500 DELAY LOOP ;"
ForthIbE.execute(:e1, script)
ForthIbE.execute(:e1, "CLOCK")
flush
{:tick, 1771153700000, :e1}
…
self() — отладочный параметр для пересылки тиковых сообщений на консоль.
Загрузку значения переменной eng-name мы выполнил лаконичным способом через функцию add_var/2, которая проверяет наличие в словаре переменной с заданным именем, при необходимости — создаёт в словаре переменную и присваивает ей заданное значение.
В цикле слово forward берет элементы для кортежа из стека и направляет сообщение в данном случае оболочке iex.
6.2. Шаблон определения бесконечного тактового генератора
: CLOCK BEGIN tick TO-ATOM TIMESTAMP eng-name @ stocks FORWARD 50 DELAY FALSE UNTIL ;
В этом примере отсылается бесконечное количество раз тот же кортеж {:tick, <метка времени>, <имя>}. Загрузка и запуск тактового генератора аналогичны предыдущему примеру.
6.3. Шаблон определения тактового цикла с окончанием цикла в заданное время
Это цикл выдачи сигнала требует большей подготовки. Обязательно должен быть запущен системный таймер:
SysTimer.start_link(nil)
Затем сформированы и загружены значениями вспомогательные переменные:
ForthIbE.execute(:e7, "VARIABLE КОНЕЧНАЯ-ОТМЕТКА")
ForthIbE.execute(:e7, "VARIABLE OFFSET")
ForthIbE.execute(:e7, "18000000!")
ForthIbE.execute(:e7, "\"2026-02-18 15:26:22.000\" text-to-unix OFFSET @ - КОНЕЧНАЯ-ОТМЕТКА ! ")
Что такое OFFSET? Это переменная приведения метки локального времени к метке времени UTC. Для временной зоны Asia/Yekaterinburg она составляет 5х60х60х1000 миллисекунд. Можно было бы выполнить приведение с помощью какой-либо библиотеки календаря в Elixir, но я посчитал, что это не в духе Forth. Помним, что все временные метки привязаны к UTC [00:00].
Затем загружаем определение CLOCK в интерпретатор
script = ": CLOCK BEGIN КОНЕЧНАЯ-ОТМЕТКА @ receive-time > WHILE 500 delay REPEAT tick to-atom КОНЕЧНАЯ-ОТМЕТКА @ eng-name @ stocks forward .\" ПОЕХАЛИ! \" ; "
ForthIbE.execute(:e7, script)
Движок в цикле запрашивает метку времени у сервера при помощи слова receive_time и сравнивает ее с заданным значением переменной КОНЕЧНАЯ-ОТМЕТКА.
В цикле точность срабатывания задана 500мс перед словом delay. Запускается сигнальный генератор как обычно:
ForthIbE.execute(:e7, "CLOCK")
После превышения полученной отметки времени над конечной тактовый генератор помимо передачи временной отметки {:tick 24379824 :e7} в целях удобства отладки выводит на консоль сообщение:
поехали!
Обращает на себя внимание периодический запрос текущего времени у системного таймера. Здесь может быть критичная ситуация, связанная с временем обработки сообщений в почтовом ящике, но другого механизма в Elixir нет.
7. Два практических примера
7.1. Совместная работа двух генераторов
Теперь запрограммируем совместную работу двух тактовых генераторов: первый сигнальный генератор будет передавать сигнал о наступлении конкретной даты и времени, в второй после этого выдаст 10 тиков с интервалом 10 сек. Собственно говоря, мы уже разобрали соответствующие шаблоны, надо только их объединить.
{:ok, pid_timer} = SysTimer.start_link(nil)
{:ok, pid_e7} = ForthIbE.start({:e7, [:e1]})
ForthIbE.add_var(:e7, "eng-name", :e7)
ForthIbE.execute(:e7, "VARIABLE КОНЕЧНАЯ-ОТМЕТКА")
ForthIbE.execute(:e7, "VARIABLE OFFSET")
ForthIbE.execute(:e7, "18000000 OFFSET !")
ForthIbE.execute(:e7, "\"2026-02-20 10:35:22.000\" text-to-unix OFFSET @ - КОНЕЧНАЯ-ОТМЕТКА ! ")
script = ": ПУСК BEGIN КОНЕЧНАЯ-ОТМЕТКА @ receive-time > WHILE 250 delay REPEAT CLOCK pass-next .\" ПОЕХАЛИ! \" ; "
ForthIbE.execute(:e7, script)
{:ok, pid_e1} = ForthIbE.start({:e1, [self()]})
ForthIbE.add_var(:e1, "eng-name", :e1)
script = ": CLOCK 10 0 DO tick TO-ATOM TIMESTAMP eng-name @ stocks FORWARD 1000 DELAY LOOP ;"
ForthIbE.execute(:e1, script)
# все готово к пуску
ForthIbE.execute(:e7, "ПУСК")
Отличие сигнального цикла процесса :e7 состоит в применении слова pass-next, которое принудительно запускает обработку скрипта на стоках, в данном случае на движке :e1.
Вывод на консоль работы двух генераторов в связке показан на заставке статьи.
7.2. АВОСТ
В разделе 6.2 мы запускали бесконечный тактовый цикл. Возникает вопрос, а как его можно остановить? Для этого нужно в цикле проверять приход сообщения АВОСТ. Покажем это на примере.
{:ok, pid_e2} = ForthIbE.start({:e2, [self()]})
ForthIbE.add_var(:e2, "eng-name", :e2)
script = ": CLOCK BEGIN 1 . 500 DELAY АВОСТ! FALSE OR UNTIL .\" Приехали \" ; "
ForthIbE.execute(:e2, script)
# пуск
ForthIbE.execute(:e2, "CLOCK")
После запуска CLOCK на экран начнет с интервалом 500 млс циклически выводиться единичка. Чтобы принудительно прервать цикл, в скрипт после задержки поставлено слово АВОСТ!, которое при получением сообщения:
send(:e2, {:avost})
выдаст -1 (TRUE) и цикл завершится по условию.
Так как цикл завершится по условию, а не по прерыванию, означает, что систему движков Forth-ibE нельзя отнести к событийно-ориентированной архитектуре (EDA), хотя «если нечто плавает как утка и крякает как утка, то это, вероятно, и есть утка».
После разработки цикла с АВОСТ как головоломка я понял, что этим приёмом с бесконечным циклом можно организовать сторожевую централизованную подсистему остановки движков. Не убивать, а именно, останавливать.
8. Количественная оценка
Как я уже говорил, полгода тому назад я принял решение разработать собственный интерпретатор Forth после того, как я не смог запустить в Elixir внешних программ интерпретатора Python больше 500.
Первым делом после разработки API оболочки Forth-ibE я провёл его испытание и загрузил 200 тыс. процессов.
Точное количество запущенных движков определялось функцией Erlang :erlang. system_info(:process_count)
Компьютер был домашний, 4-х ядерный. Движки были не "залиты" кодом, но, имхо, определения занимают мало места. Например, из предыдущего раздела видно, что определение тактового генератора занимает около 20 слов. Увеличение размера стека данных, по идее, такого же порядка.
Но мне кажется неправильно, использовать традиционный критерий оценки — количество процессов на вычислитель, так как он соответствует доминирующей модели массового обслуживания и конкурирующего доступа к ресурсам. Эта модель не айс — жесткая, не гибкая, требующая концентрации больших централизованных ресурсов.
Ей на смену должна прийти модель распределенного параллельного вычисления на кластерах компьютеров. Чем она лучше? Отвечу как думаю: легко перенастраивается, гибкая, развертывается на ограниченных децентрализованных ресурсах. Вижу один недостаток — потребуются глубокое переосмысление семантического разрыва между пользователями и программистами и переподготовка кадров.
Всё–таки закончу про испытания. Так вышло, что для облегчения отладки движков словарь движка был разделен на таблицу встроенных функций и пользовательский словарь переменных и определений Forth. Затем после переработки таблицы встроенных функций в разделяемую таблицу ETS тест показал 1 млн. одновременно загруженных движков. Далее я не проверял.
9. Резюме и предполагаемая области применения системы
Демонстратор системы движков относится к классу приложений обработки данных в потоке. Известные системы обработки данных в потоке применяют технологию подписки на данные через брокеров. Это сильно удорожает комплексные решения. Проектируемый демонстратор движков лишен брокерских элементов, т.к. данные двигаются принудительно, как бы точнее выразиться, по принципу full-push.
В силу неопределенности времени получения и обработки поступающих сообщений можно заранее предсказать, что областью применение рассмотренной системы взаимодействия движков через потоки данных являются системы "мягкого" реального времени для использования, например, в обычных бытовых приборах. Для управления космическими ракетами система не предназначена.
10. Размещение на GitHub
Зонтичный проект демонстратора системы межузлового взаимодействия движков Forth будет выложен на GitHub после подключения к системе планировщика и полноценных движков. На GitHub https://github.com/VAK-53/Forth-ibE выложена актуальная версия интерпретатора Forth-ibE для терминальной работы.
11. Благодарность
У меня проходил интенсивный обмен мнениями на VK со специалистом Forth, пожелавшим остаться инкогнито. Тем не менее, я считаю необходимым высказать ему глубокое чувство благодарности и признательности за полезные консультации.
Литература
1. Л.Броуди, Начальный курс программирования на языке Форт: Пер. с англ.; – М.: Финансы и статистика, 1990.
2. C.Юрич, Elixir в действии / Пер. с англ.; – М.: ДМК Пресс, 2020.
3. https://fforum.winglion.ru/ https://github.com/alexiob/forthvm
4. https://clustrmaps.com/site/1i3k
5. https://habr.com/ru/articles/985894/
6. https://www.wired.com/2012/07/leap-second-glitch-explained/
7. https://learnyousomeerlang.com/time
8. https://www.erlang.org/doc/apps/erts/time_correction#os-system-time
9. RFC 3339, Date and Time on the Internet: Timestamps, 2002
10. https://habr.com/ru/companies/selectel/articles/784264/
11. Forth, Американский Национальный Стандарт, 1994.
Дополнение к ТУ на интерпретатор Forth-ibE
1. Движки интерпретатора Forth_ibE реализованы как процессы OTP GenServer и доступны через функций API:
execute(eng, words) — запуск выполнения набора words, eng — системное имя рабочего процесса движка;
add_var(eng, name, value) — установить переменную name со значением value в движке eng;
get_var(eng, name) — получить значение переменной name в движке eng.
2. Слова для межпроцессного взаимодействия
PASS-NEXT ( w -- ) — обёртка функции API execute/1, список адресов получателей хранится в состоянии движка;
FORWARD ( tg, ts, nm -- ) — формирует кортеж из данных стека и отправляет его ожидающим движкам; адреса маршрутизации хранятся в состоянии движка. Это некоторый аналог слова .S, работающий не через консоль, а с сообщениями. В отличие от .S слово forward освобождает стек, который опять становится готовым для приёма данных. Слово введено для отладочных целей.
Условные обозначения в нотации слов:
w — символьная строка определения слова Forth или исполнения,
tg — тег сообщения в виде атома Elixir,
ts — метка времени Unix,
nm — имя отправителя Unix.
3. Для работы с временными метками и временем и для интерфейса движка с пользователем реализованы следующие слова интерпретатора:
TS-TO-UNIX ( dt -- ts ) — преобразует дату и время Elixir в целое число временной метки Unix;
TS-FROM-UNIX ( ts -- dt ) — преобразует целое число временной метки Unix в дату и время Elixir;
TIME&DATE ( dt -- x1,x2,x3,x4,x5,x6 ) — в соответствии со стандартом Forth помещает в стек 6 атрибутов текущего времени <секунда>, <минута>, <час>, <день>, <месяц> и <год>.
TIMESTAMP ( -- dt ) — помещает в стек дату и время в формате Elixir.
Условные обозначения в нотации слов:
td — дата и время в формате Elixir,
xi — поля словаря даты и времени Elixir,
4. Для строительства циклов с задержкой и интерфейса между движками реализованы следующие слова интерпретатора:
DELAY — ( n -- ) останавливает работу движка на заданную временную задержку в миллисекундах,
MS — ( n -- ) слово, равнозначное DELAY, установленное стандартом ANS,
RECEIVE_TIME — ( -- ts ) производит запрос у :sys_timer метки времени и помещает его на вершину стека данных,
5. Вспомогательные слова интерпретатора:
TO-ATOM ( s -- a ) — преобразует символьное слово из стека в атом Elixir;
TO-LIST ( st -- [a]) — преобразует символьные слова из стека в список атомов Elixir;
TO-TIMESTAMP ( s -- ts ) — преобразует символьную строку в метку времени;
STOCKS ( -- [a]) — возвращает из состояния список стоков движка;
DELETE-STOCK ( a -- ) — удаляет получателя из списка стоков состояния движка.
Условные обозначения в нотации слов:
s — символьная строка Elixir,
a — атом Elixir,
[a] — список атомов Elixir,
st — элементы стека данных.
6. Единственный OPT GenServer с глобальным именем sys_timer централизованно раздаёт глобальное время движкам.
7. Единая разделяемая таблица встроенных функций реализована отдельно с глобальным именем sys_table.
8. Таблица встроенных слов Forth-ibE расширена словом DICTIONARY, которое выводит на консоль пользователя содержимое словаря.
9. В Forth-ibE реализован цикл BEGIN… WHILE… REPEAT.
10. Одним из исполнений движка средствами Forth может быть тактовый генератор.
Тактовые генераторы строятся на базе штатных циклов Forth и встроенного слова delay. Цикл может пересылать сообщение при помощи слова FORWARD в виде кортежа:
{:tick, <timestamp>, <отправитель> }
Имеются примеры паттернов различных типов тактовых генераторов.
11. Для задания стандартной даты и времени с разделительным пробелами (RFC 3339) пришлось расширить стандарт Forth и ввести примыкающие кавычки, которые обрамляют тексты с пробелами. Например: фрагмент
“2026.02.03 19:00:00,000”
интерпретируется как единая символьная строка, поступающая в стек, которую затем можно обработать для получения метки времени.
