1. BackgroundA month ago, I implemented a Forth interpreter in Elixir, which I described on Habr. This hybrid was given the compound name Forth-ibE in honor of its parents (Forth in-build Elixir).The next step in development was defining a messaging API for a distributed Forth developer team to collaborate.The reader will likely have questions like "why" and "why?" Therefore, it is now necessary to describe the solution to several initial difficulties.First, [1] states that"Along with single-task Forth systems, there are also multitask Forth systems. They can handle any number of tasks. A task can be either a terminal task, in which all interactive Forth power is transferred to an operator sitting at a terminal, or a control system, which provides control over equipment without a terminal.A control task has a pair of stacks and a small set of user variables. Since a terminal is not used when executing a control task, it does not require
This signal output cycle requires more preparation. The system timer must be started.:
SysTimer.start_link(nil)
Then auxiliary variables are generated and loaded with values.:
ForthIbE.execute
e7, "VARIABLE ENDPOINT")
ForthIbE.execute
e7, "VARIABLE OFFSET")
ForthIbE.execute
e7, "18000000!")
ForthIbE.execute
e7, "\"2026-02-18 15:26:22.000\" text-to-unix OFFSET @ THE- END-MARK ! ")
What is OFFSET? This is a variable for converting the local time stamp to the UTC time stamp. For the Asia/Yekaterinburg time zone, it is 5x60x60x1000 milliseconds. It would be possible to perform the cast using some calendar library in Elixir, but I thought that this was not in the spirit of Forth. Please note that all timestamps are linked to UTC [00:00].
Then we load the CLOCK definition into the interpreter.
script = ": CLOCK BEGIN END-MARK @ receive-time > WHILE 500 delay REPEAT tick to-atom END-MARK @ eng-name @ stocks forward .\"LET'S GO! \" ; "
ForthIbE.execute
e7, script)
In a loop, the engine requests a timestamp from the server using the word receive_time and compares it with the set value of the END-TIMESTAMP variable.
In the cycle, the trigger accuracy is set to 500ms before the word delay. The signal generator starts as usual:
ForthIbE.execute
e7, "CLOCK")
After exceeding the received timestamp over the final one, the clock generator, in addition to transmitting the timestamp {:tick 24379824 :e7}, outputs a message to the console for ease of debugging.:
Let's go!
Attention is drawn to the periodic request of the current time from the system timer. There may be a critical situation related to the processing time of messages in the mailbox, but there is no other mechanism in Elixir.
7. Two practical examples
7.1. Joint operation of two generators
Now we will program the joint operation of two clock generators: the first signal generator will transmit a signal about the occurrence of a specific date and time, and the second one will then output 10 ticks with an interval of 10 seconds. As a matter of fact, we have already sorted out the relevant templates, we just need to combine them.
{
k, pid_timer} = SysTimer.start_link(nil)
{
k, pid_e7} = ForthIbE.start({:e7, [:e1]})
ForthIbE.add_var
e7, "eng-name", :e7)
ForthIbE.execute
e7, "VARIABLE ENDPOINT")
ForthIbE.execute
e7, "VARIABLE OFFSET")
ForthIbE.execute
e7, "18000000 OFFSET !")
ForthIbE.execute
e7, "\"2026-02-20 10:35:22.000\" text-to-unix OFFSET @ THE- END-MARK ! ")
script = ": START BEGIN END-MARK @ receive-time > WHILE 250 delay REPEAT CLOCK pass-next .\"LET'S GO! \" ; "
ForthIbE.execute
e7, script)
{
k, 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)
# everything is ready to launch
ForthIbE.execute
e7, "START")
The difference between the signal cycle of the process :e7 is the use of the word pass-next, which forcibly starts processing the script on the drains, in this case on the engine :e1.
The output to the console of the operation of two generators in combination is shown on the article's splash screen.
This signal output cycle requires more preparation. The system timer must be started.:
SysTimer.start_link(nil)
Then auxiliary variables are generated and loaded with values.:
ForthIbE.execute
ForthIbE.execute
ForthIbE.execute
ForthIbE.execute
What is OFFSET? This is a variable for converting the local time stamp to the UTC time stamp. For the Asia/Yekaterinburg time zone, it is 5x60x60x1000 milliseconds. It would be possible to perform the cast using some calendar library in Elixir, but I thought that this was not in the spirit of Forth. Please note that all timestamps are linked to UTC [00:00].
Then we load the CLOCK definition into the interpreter.
script = ": CLOCK BEGIN END-MARK @ receive-time > WHILE 500 delay REPEAT tick to-atom END-MARK @ eng-name @ stocks forward .\"LET'S GO! \" ; "
ForthIbE.execute
In a loop, the engine requests a timestamp from the server using the word receive_time and compares it with the set value of the END-TIMESTAMP variable.
In the cycle, the trigger accuracy is set to 500ms before the word delay. The signal generator starts as usual:
ForthIbE.execute
After exceeding the received timestamp over the final one, the clock generator, in addition to transmitting the timestamp {:tick 24379824 :e7}, outputs a message to the console for ease of debugging.:
Let's go!
Attention is drawn to the periodic request of the current time from the system timer. There may be a critical situation related to the processing time of messages in the mailbox, but there is no other mechanism in Elixir.
7. Two practical examples
7.1. Joint operation of two generators
Now we will program the joint operation of two clock generators: the first signal generator will transmit a signal about the occurrence of a specific date and time, and the second one will then output 10 ticks with an interval of 10 seconds. As a matter of fact, we have already sorted out the relevant templates, we just need to combine them.
{
{
ForthIbE.add_var
ForthIbE.execute
ForthIbE.execute
ForthIbE.execute
ForthIbE.execute
script = ": START BEGIN END-MARK @ receive-time > WHILE 250 delay REPEAT CLOCK pass-next .\"LET'S GO! \" ; "
ForthIbE.execute
{
ForthIbE.add_var
script = ": CLOCK 10 0 DO tick TO-ATOM TIMESTAMP eng-name @ stocks FORWARD 1000 DELAY LOOP ;"
ForthIbE.execute
# everything is ready to launch
ForthIbE.execute
The difference between the signal cycle of the process :e7 is the use of the word pass-next, which forcibly starts processing the script on the drains, in this case on the engine :e1.
The output to the console of the operation of two generators in combination is shown on the article's splash screen.