It's time to talk about another side of using the wonderful Fort language.
In this series of articles, I will show how you can use it to create tiny client-server applications. Each of which can be used as a research and teaching tool.
For entertainment, we need Windows up to the seven and the SP-Forth package. Andrei Tcherezov declared the possibility of running his fort system under Linux, but I didn’t check it.
To begin with, we will try to create a simple server program that will give to the joining client well ... let's say the current date and time on the server.
Create a datetimes.f file in Notepad or any other text editor (Akelpad) in the same folder as spf4.exe.
')
What do we need to create a server? Sockets!
To start working with them you need to connect the appropriate library.
~ nn \ lib \ sock2.fConveniently, all libraries are stored in source code. You can always look and see what it is and how it works.
0 VALUE sockt
0 VALUE sockt_a
SocketsStartup THROW. (Sockets started) CR
CreateSocket THROW TO sockt. (Socket created) CR
The first two lines - obviously the definition of variables. VALUE variables are an improvement to the classic Fort. They allow you to avoid endless dereferencing (getting the value to the address) and make the text more readable.
The third line speaks for itself, but at the last it is necessary to give a couple of comments.
The word CreateSoket is understandable and true, but there are nuances. Sockets are different! Honestly, I don’t know much about socket sorts, but the simplest and, it seems, the most frequently used is a blocking bidirectional streaming TCP / IP socket created by this word. How to create a different type of sockets, you can look into the library. The magic word THROW just intercepts the error codes and throws an exception if the error code is non-zero. The brackets print the text between them on the terminal, CR - line feed.
80 sockt BindSocket THROW. (Socket binded) CR
sockt ListenSocket THROW. (Listen) CR
sockt AcceptSocket THROW TO sockt_a. (Accept connection from :)
That's almost all. Now we sit and listen on port 80 until someone knocks.
Of course, you can choose a completely arbitrary free port.
The word AcceptSoket will stop the execution of the program until it is connected.
Who joined us?
sockt_a GetPeerIP & Port THROW SWAP NtoA TYPE. "port:".
will display in the console the IP address and port from which the connection occurred. The only SWAP in the whole program arose only because of the not very thoughtful implementation of the word
GetPeerIP & Port .
Prepare the date and time for shipment.
We will need another library.
lib \ include \ facil.fFrom which you need the word
TIME & DATE . (I had to fix it in the library, now it is different from the complete one. It began to give out milliseconds, and the date order has changed.) This word puts 7 values on the stack. Milliseconds, seconds, hours, year, month, day. Accordingly, the first number will be removed from the stack. This is the date of the month.
S "Current time: sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (day)
S "-" sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (month)
S "-" sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (year)
S "" sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (hours)
S "-" sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (minutes)
S "-" sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (seconds)
S "-" sockt_a WriteSocket THROW
S> D (D.) sockt_a WriteSocket THROW (milliseconds)
It looks awful. The beauty of the Fort is that this ugliness can be turned into candy.
It can be seen that the same text is repeated 7 and twice in 3. Of course, you can put a cycle on it, but then the text will be difficult to read.
Take advantage of the possibilities of the Fort.
Define additional words
: send_value S> D (D.) sockt_a WriteSocket THROW;
: send_delimeter sockt_a WriteSocket THROW;
Logical, understandable and easy to read.
{Remark: The word S> D extends the top element of the stack to double precision with a sign. The word (D.) converts the double precision number to a string and puts its address and counter on the stack.}
: Day send_value;
: Month send_value;
: Year send_value;
: Hours send_value;
: Minutes send_value;
: Seconds send_value;
: Milliseconds send_value;
Still awful. What can be done?
One key feature of the Fort language comes to the rescue. We can create our own defining words.
: make_value CREATE DOES> DROP send_value;
: make_values 0 DO make_value LOOP;
During its execution, the word
make_value will select the next set of characters, limited by spaces, from the input stream and place it on top of the current dictionary. The semantics of this new word will be
assigned to the word after
DOES> . That is, we have a tool with which you can create concepts that are united by common semantics.
7 Make_values Day Month Year Hours Minutes Seconds Milliseconds
And for delimiters
: make_delimeter CREATE, DOES> 1 send_delimeter;
: make_delimeters 0 DO make_delimeter LOOP;
BL CHAR - CHAR: 3 make_delimeters: - _
Now we can write nicely
TIME & DATE
Day - Month - Year _ Hours: Minutes: Seconds: Milliseconds
We break connection
sockt_a CloseSocket THROW
And clear the sockets
SocketsCleanup THROW
Everything.
As you can see the stages of execution and compilation can alternate. This is another basic feature of the Fort. Also a well-written Fort program requires almost no comments.
Those who were attentive enough noticed that we had redefined the basic words colon and minus. How to deal with this? The answer is below.
Combed code
~ nn \ lib \ sock2.f
lib \ include \ facil.f
80 CONSTANT port
0 VALUE sockt
0 VALUE sockt_a
: send_value S> D (D.) sockt_a WriteSocket THROW;
: send_delimeter sockt_a WriteSocket THROW;
: make_value CREATE DOES> DROP send_value;
: make_values 0 DO make_value LOOP;
7 Make_values Day Month Year Hours Minutes Seconds Milliseconds
: make_delimeter CREATE, DOES> 1 send_delimeter;
: make_delimeters 0 DO make_delimeter LOOP;
: define::;
BL CHAR - CHAR: 3 make_delimeters: - _
define: time & date_server
SocketsStartup THROW. "Sockets started" CR
CreateSocket THROW TO sockt. "Socket created" CR
port sockt BindSocket THROW. "Socket binded" CR
sockt ListenSocket THROW. "Listen" CR
sockt AcceptSocket THROW TO sockt_a. "Accept connection from:"
sockt_a GetPeerIP & Port THROW SWAP NtoA TYPE. "port:".
S "Current time:" sockt_a WriteSocket THROW
TIME & DATE
Day - Month - Year _ Hours: Minutes: Seconds: Milliseconds
sockt_a CloseSocket THROW
SocketsCleanup THROW
;
time & date_server