fig-FORTH Wörter für #FujiNet.


fig-FORTH Wörter für #FujiNet.

von tschak909 » Sa 3. Apr 2021, 19:34
Ich arbeite an N: Bindungen für FORTH. Hier ist eine kleine Erklärung des NOPEN-Wortes:

```forth

SCR # 9
0 ( N: OPEN )
1
2 : NOPEN ( aux2 aux1 str Nx: -- )
3 DUNIT C! ( Nx: to DUNIT )
4 ->DSPEC ( string into DSPEC )
5 DSPEC DBUF ! ( point DBUF to DSPEC )
6 DAUX1 C! ( set DAUX1 to aux1 )
7 DAUX2 C! ( set DAUX2 to aux2 )
8 71 DDEVIC C! ( DDEVIC = $71 FN N: )
9 4F DCOMND C! ( DCOMND = 'O' )
10 80 DSTATS C! ( DSTATS = $80 -> FN )
11 100 DBYT ! ( DBYT = 256 bytes )
12 F DTIMLO ! ( DTIMLO = 15 secs )
13 (DO-SIOV) ;
14
15 -->

```

Zunächst ein Beispiel für die Verwendung:

```forth

0 12 " N:TCP://bbs.fozztexx.net:23/" N1: NOPEN ok

```

NOPEN nimmt vier Parameter entgegen:

* den aux2-Wert (Übersetzungsmodus)
* der aux1-Wert (Lesen/Schreiben/etc)
* der Devicespec (wird als Inline-String angegeben)
* die Unit-Nummer (das Wort N1: ist hier ein Alias für 1)

Diese Werte werden vom Stack gepoppt und sofort den entsprechenden Adressen im Speicher zugewiesen, die dem Device Control Block (DCB) an Adresse $0300 entsprechen

Für die meisten von ihnen werden die Wörter C! und ! verwendet, um ihre Werte zu setzen, und sie nehmen beide die gleichen Parameter:

```
<value> <address> C!
<value> <address> !
```

Da diese Werte vom Stack kommen, erhält das C! neben DUNIT seinen Wert vom nächsten Wert auf dem Stack.

->DSPEC ist ein Wort, das auf dem vorherigen Bildschirm definiert wurde, um den Inline-String auf dem Stack schnell in einen 256-Byte-Puffer namens DSPEC zu übertragen (der gelöscht wird, bevor er verschoben wird)

Es ist hier definiert, zusammen mit den Worten DSPEC und CL/DSPEC, um den kleinen Puffer zu verwalten:

```forth
SCR # 8
0 ( N: DEVICESPEC WORDS )
1
2 ( BUFFER TO HOLD DEVICESPEC )
3 0 VARIABLE DSPEC 254 ALLOT
4
5 ( CLEAR DEVICESPEC )
6 : CL/DSPEC DSPEC 256 0 FILL ;
7
8 ( SET DEVICESPEC )
9 : ->DSPEC
10 CL/DSPEC
11 DSPEC <-STRING ;
12
13
14
15 -->
```

(warum 254? Variable teilt automatisch 2 Bytes zu, wir brauchen nur 254 mehr für 256).

und <-STRING ist Teil eines kleinen Satzes von String-Wörtern, die die " und <-STRING-Wörter definieren, die wir benötigen, um einfach In-Line-Strings zu erstellen und in Puffer zu legen:

```forth
SCR # 7
0 ( N: STRING WORDS )
1
2 : ["]
3 R COUNT DUP 1+ R> + >R ;
4
5 : " ( start embedded string )
6 22 STATE @ IF
7 COMPILE ["] WORD HERE C@ 1+ ALLOT
8 ELSE
9 WORD HERE DUP C@ 1+ PAD SWAP CMOVE PAD COUNT
10 THEN ; IMMEDIATE
11
12 : <-STRING ( src len dest -- )
13 2DUP + >R
14 SWAP CMOVE R> 0 SWAP C! ;
15 -->
```

Ja, Bildschirm 7 sieht furchterregend aus, aber um es so einfach wie möglich zu erklären: Sie wollen keine Zeichenketten interpretieren, Sie wollen nur, dass der Interpreter sie überspringt und zum nächsten Ding springt, das er interpretieren kann, und es lange genug im Speicher behält, um es in einen Puffer zu legen. Das ist es, was die obigen Wörter tun, wobei berücksichtigt wird, dass ein Forth-Programmierer in der Lage sein will, Inline-Strings entweder in ein Wort kompiliert oder interaktiv zu verwenden. Das ist es, was die ersten beiden Wörter implementieren. Das dritte Wort ->STRING nimmt die Adresse, die durch das " Wort erzeugt wurde, sowie deren Länge und verschiebt sie in den Zielpuffer.

Mit diesen Worten haben Sie alles, was Sie brauchen, um NOPEN zu implementieren, das einen Weg brauchte, um einen devicespec-String anzugeben.

Es bleibt nur noch die Frage, was ist (DO-SIOV)?

Es ist ein in Assembler implementiertes Wort, das den SIO-Vektor (Routinen) im Atari aufruft, der die Werte nimmt, die wir zuvor auf den DCB gelegt haben, und die angeforderte I/O-Operation ausführt.

Es ist so in Forth Assembler implementiert:

```
SCR # 3
0 ( SIOV )
1
2 CODE (DO-SIOV)
3
4 XSAVE STX, ( SAVE X REGISTER )
5 SIOV JSR, ( CALL SIOV)
6 XSAVE LDX, ( RESTORE X REGISTER )
7 NEXT JMP, ( RET TO INTERPRETER )
8
9
10
11
12
13
14
15 -->
```

Da das X-Register vom fig-FORTH-Interpreter ausgiebig genutzt wird und auch SIOV das X-Register nutzt, müssen wir den Inhalt des X-Registers an einer Stelle zwischenspeichern, die fig-FORTH für diesen Zweck bereitstellt. Ansonsten rufen wir einfach SIOV auf, stellen das X-Register wieder her und springen zurück in den Interpreter. Das CODE-Wort ist ein spezielles Wort in Forth, das anzeigt, dass das folgende Wort für Assembler-Code ist, der mit dem Forth-Assembler eingegeben werden soll. Anweisungen werden in Stack-Reihenfolge eingegeben, mit dem Operanden an erster Stelle, gefolgt von 'Ablage'-Wörtern, die mit einem Komma enden und anzeigen, dass die Anweisung am nächsten verfügbaren Byte im Speicher abgelegt werden soll.

Seltsam? Ja.

Warum mache ich das? Um zu zeigen, daß das FujiNet überall auf dem Atari eingesetzt werden kann, auch auf Systemen, die kein formales Dateisystem haben, wie z.B. fig-Forth.

-Thom

Re: fig-FORTH Wörter für #FujiNet.

von tschak909 » So 4. Apr 2021, 08:12
Ein weiteres Beispiel für die Verwendung des #FujiNet von Forth ist, Dinge wie die Anzeige der aktuell verwendeten Host- oder Festplatten-Slots zu tun. Dieses Beispiel konzentriert sich auf das FLH-Wort, das die verfügbaren Host-Slots anzeigt.

Wir beginnen mit einer Reihe von Konstanten, die auf die von der SIO verwendeten Speicherplätze abgebildet werden. Diese machen den Code leichter lesbar und werden von allen Wörtern des FUJINET-Vokabulars in Forth verwendet:

```forth
SCR # 1
0 ( #FN SIO CONSTANTS 1 )
1
2 BASE @ HEX
3
4 VOCABULARY FUJINET
5
6 0300 CONSTANT DDEVIC 0301 CONSTANT DUNIT
7 0302 CONSTANT DCOMND 0303 CONSTANT DSTATS
8 0304 CONSTANT DBUF 0306 CONSTANT DTIMLO
9 0307 CONSTANT DRESVD 0308 CONSTANT DBYT
10 030A CONSTANT DAUX1 030B CONSTANT DAUX2
11 030A CONSTANT DAUX
12
13 E459 CONSTANT SIOV
14
15 -->
```

Wir brauchen auch Bildschirm 3, der das (DO-SIOV)-Wort liefert, das dem Atari tatsächlich sagt, dass er die SIO-Operation durchführen soll.


```forth
SCR # 3
0 ( SIOV )
1
2 CODE (DO-SIOV)
3
4 XSAVE STX, ( SAVE X REGISTER )
5 SIOV JSR,
6 XSAVE LDX, ( REST X REGISTER )
7 NEXT JMP, ( RET TO INTERPRET )
8
9
10
11
12
13
14
15 -->
```

Wir benötigen einen Platz im Speicher, um die Ergebnisse unserer Host-Liste zu speichern, sowie eine Möglichkeit, jeden Host-Slot einfach aus diesem Speicher abzurufen. Dies können wir erreichen, indem wir ein Wort angeben, das uns diese Eigenschaften verleiht, HOST-ARRAY. HOST-ARRAY alloziert einen 256 Byte großen Speicherbereich, wenn wir es erstellen, und wenn wir es benutzen, gibt es den Offset für den Host-Slot zurück, nach dem wir fragen. Dies ist möglich, weil Forth Wörter definieren kann, die beim Erzeugen eine Sache tun und beim Verwenden eine andere. Dieses Konzept nennt man Meta-Programmierung. Mit dem Wort <BUILDS> kann der Programmierer angeben, welche Wörter ausgeführt werden sollen, wenn die Definition kompiliert oder "gebaut" wird, und mit DOES> kann der Programmierer angeben, welche Wörter ausgeführt werden, wenn das Wort angetroffen wird.

```forth
SCR # 70
0 ( FLH - Data Structures )
1
2 : HOST-ARRAY ( 8 entries 32 bytes )
3 <BUILDS 256 ALLOT ( 256 Bytes )
4 DOES> SWAP 32 * + ; ( each entry 32 bytes )
5
6 ( Create a HOST-ARRAY called FLHBUF )
7
8 HOST-ARRAY FLHBUF
9
10
11
12
13
14
15 -->
```

HOST-ARRAY ist als Compilerwort definiert und wird sofort verwendet, um ein HOST-ARRAY namens FLHBUF zu erzeugen, in dem die Host-Liste gespeichert wird.

Der <BUILDS-Abschnitt ist einfach genug, er erzeugt einen 256-Byte-Bereich, in dem Daten gespeichert werden können. Dieser unterteilt sich in 8 Slots, die jeweils 32 Zeichen enthalten.

Der DOES>-Abschnitt ist etwas komplizierter, da DOES> eine Adresse, die dem ersten verfügbaren Datenbyte entspricht, auf den Stack legt, und wir dann den Stack leicht vertauschen (weil er momentan rückwärts läuft), so dass wir diese Adresse mit (32 * n) multiplizieren können, wobei n der übergebene Parameter ist.

Dadurch wird ein Datentyp erzeugt, der wie folgt funktioniert:

```forth
0 FLHBUF . 16304 ( the address in memory of the first host slot )
1 FLHBUF . 16336 ( the address in memory of the second host slot )
2 FLHBUF . 16368 ( the third entry ... and so on )
```

Wir haben also einen Datentyp, der die ganze schwere Arbeit für uns erledigt, wenn wir mit dem Lesen beginnen wollen.

Damit können wir nun ein Wort (FLH) erstellen, das die Host-Liste in unseren neu erstellten Puffer einliest.

```
SCR # 71
0 ( FLH - Get Host List )
1
2 BASE @ HEX
3
4 : (FLH) ( -- )
5 70 DDEVIC C!
6 01 DUNIT C!
7 F4 DCOMND C!
8 40 DSTATS C!
9 0 FLHBUF DBUF !
10 0F DTIMLO C!
11 0100 DBYT !
12 0000 DAUX !
13 (DO-SIOV) ;
14
15 BASE ! -->
```

(FLH) verwendet die oben definierten FN-SIO-Konstanten, um den Code leichter lesbar zu machen, und setzt alle Werte, die wir benötigen, in die DCB-Tabelle (Device Control Block) im Speicher.

* DDEVIC ist auf $70 gesetzt, das ist die SIO-Geräte-ID für das #FujiNet-Steuergerät
* DUNIT wird auf $01 gesetzt, da wir uns speziell auf das Gerät $70 beziehen wollen.
* DCOMND wird auf $F4 gesetzt, das ist der hier definierte Befehl "Get Host Slot List": ( https://github.com/FujiNetWIFI/fujinet- ... Host-Slots )
* DSTATS ist auf $40 gesetzt, was angibt, dass der Atari eine Nutzlast an Daten vom FujiNet erwartet.
* DBUF wird auf die Adresse ganz oben im FLHBUF gesetzt. Da alle Host-Slots in FLHBUF zusammenhängend sind, brauchen wir nur den oberen Teil des Puffers anzugeben, und wir können sie alle auf einmal einlesen.
* DTIMLO wird auf $0F gesetzt, ein Standardwert, der bedeutet, dass 15 Sekunden auf eine Antwort gewartet wird.
* DBYT ist auf $0100 gesetzt, das ist 256 in Hex, was bedeutet, dass wir eine Nutzlast von 256 Bytes vom FujiNet erwarten.
* DAUX ist auf $0000 gesetzt. Er wird nicht verwendet.

Sobald diese Werte gesetzt sind, wird das (DO-SIOV)-Wort verwendet, um die SIO-Operation durchzuführen.

Das (FLH)-Wort tut nichts auf dem Stack und gibt nichts aus. Es ist ein rein prozedurales Wort, das benötigt wird, um die Host-Liste zu erhalten und sie in unseren Puffer zu legen.

Wenn wir einen Blick in den Puffer werfen würden, sähen wir etwa so aus:

```forth
0 FLHBUF 256 CDUMP

3000 SD--------------
3010 ----------------
3020 irata.online----
3030 ----------------
3040 fujinet.online--
3050 ----------------
3060 fujinet.pl------
3070 ----------------
3080 ----------------
3090 ----------------
30A0 ----------------
30B0 ----------------
30C0 ----------------
30D0 ----------------
30E0 ----------------
30F0 ----------------
```

die Bindestriche stehen hier für das Null-Zeichen, auf dem Atari würden sie durch das Herz-Zeichen dargestellt. Da die FujiNet-Firmware in C implementiert ist, sind die Strings null-terminiert, und der einfachste Weg, die Daten anzuzeigen, ist, das Null-Zeichen einfach nicht anzuzeigen. Wir können dies über eine Bedingung erreichen, die das EMIT-Wort nur verwendet, um das Zeichen anzuzeigen, wenn es nicht null ist. Hier machen wir ein Wort HEMIT, das genau das tut:

```forth
SCR # 72
0 ( FLH - HEMIT word )
1
2
3
4 ( EMIT, but ignore null chars )
5
6 : HEMIT ( n -- )
7 DUP ( because 0= consumes it )
8 0= IF ( is char a null? )
9 DROP ( yes, drop it. )
10 ELSE ( otherwise ... )
11 EMIT ( Display it. )
12 THEN ; ( Done.)
13
14
15 -->
```

HEMIT erwartet, wie EMIT, eine Ein-Byte-Zahl. Wenn diese Zahl 0 ist, wird nichts angezeigt.

```forth
HEMIT 65 Aok
HEMIT 66 Bok
HEMIT 0 ok
```

Bei der Eingabe von HEMIT wird die eingehende Zahl sofort DUPLIZIERT, denn 0= wird sie verbrauchen und durch eine 1 ersetzen, wenn sie übereinstimmt, oder durch eine 0, wenn sie nicht übereinstimmt. IF wird die 0 oder die 1 verbrauchen. Wenn der Wert 1 ist, wurde uns eine NULL übergeben, und die Zahl, die wir zuvor dupliziert haben, wird DROPPed (wenn Sie eine Sauerei machen, räumen Sie sie auf.), andernfalls übergeben wir die duplizierte Zahl an EMIT, das sie anzeigt.

Mit dem HEMIT-Wort in der Hand haben wir nun alles, was wir brauchen, um ein Wort zu erstellen, .FLH", das den Namen eines Host-Slots anzeigt. Es benötigt einen Parameter, nämlich die Nummer des Host-Slots, die angezeigt werden soll.


```forth
SCR # 73
0 ( FLH - Display Host slot n )
1
2 0 VARIABLE HOSTSLOT ( Temporary variable for slot )
3
4
5 : .FLH" ( n -- )
6 HOSTSLOT C! ( save n )
7 32 0 DO
8 HOSTSLOT C@ FLHBUF ( Beginning of Hostslot )
9 I + ( next character )
10 C@ ( Get Character )
11 HEMIT ( And display it.)
12 LOOP ; ( Done. )
13
14
15 -->
```

Um diesen Code leichter lesbar zu machen (und nicht auf das Jonglieren mit dem Rückgabestapel zurückgreifen zu müssen), wird eine Variable namens HOSTSLOT definiert, die auf den übergebenen Parameter gesetzt wird. Wir richten dann eine Schleife ein, die zuerst die Adresse des Host-Slots abruft, den Schleifenindex dazu addiert, die Byte-Nummer an dieser Adresse abruft und sie dann an HEMIT zum Drucken weitergibt, und dann in einer Schleife zurückläuft, bis alle 32 Zeichen gedruckt sind.

Dies erzeugt ein Wort, das sich wie folgt verhält:


```forth
(FLH) ok
0 .FLH" SD ok
1 .FLH" irata.online ok
2 .FLH" fujinet.online ok
```

...und so weiter.

Es wird dann eine triviale Angelegenheit, ein einfaches Wort zu schreiben, das dies für alle 8 Steckplätze tut:

```forth
SCR # 74
0 ( FLH - Display all Host Slots )
1
2
3
4 : FLH ( -- )
5 CR ( start on new line )
6 (FLH) ( Get host slots )
7 8 0 DO ( 0 to 7 )
8 I U. ." : " ( show slot # )
9 I .FLH" ( Display host slot I )
10 CR ( CR )
11 LOOP ; ( done. )
12
13
14
15 ;S
```

Das (FLH)-Wort beginnt mit dem (CR)-Wort, um sicherzustellen, dass die Ausgabe in der nächsten Zeile beginnt, sonst würde die Ausgabe sofort in der gleichen Zeile wie FLH beginnen. Das (FLH)-Wort wird aufgerufen, um die Host-Liste zu holen, und es wird eine neue Schleife von 8 Iterationen durchgeführt. Innerhalb dieser Schleife wird die Indexvariable I vom (U)-Wort verwendet, um die aktuelle Slotnummer anzuzeigen, gefolgt von einem Inline ." : ", um anschließend einen Doppelpunkt auszugeben. I wird dann auch als Eingabe für .FLH" verwendet, um den Wert des betreffenden Host-Slots anzuzeigen. Anschließend verwenden wir wieder das CR-Wort, um den Cursor in die nächste Zeile zu setzen und die Schleife zu beenden.

Das Ergebnis sieht wie folgt aus:

```forth
FLH
0 : SD
1 : fujinet.online
2 : atari-apps.irata.online
3 : fujinet.pl
4 : TMA-2
5 : RASPBERRYPI
6 :
7 :
ok
```



Am Ende des Tages haben wir ein Wort, FLH, das auf vorherigen Wörtern aufbaut, um eine Host-Liste auszugeben. Aber dies ist mehr als ein Befehl, dies ist ein Wort, das nun Teil der Sprache ist. Das bedeutet, dass es nicht nur interaktiv verwendet werden kann, sondern auch aus anderen Wörtern heraus verwendet werden kann:

```forth
: MYHOSTLIST ." This is my host list: " CR FLH ;
```

Und selbst die einzelnen Wörter, aus denen FLH besteht, können in anderen Wörtern verwendet werden. Im Wesentlichen wird die Sprache um einen Satz von Wörtern erweitert, die sinnvolle Dinge mit dem FujiNet tun können.

Auf die gleiche Weise können andere Wörter hinzugefügt werden, die die Fähigkeiten der Umgebung erweitern. Dies ist ein Teil dessen, was einige Leute zu Forth hingezogen hat, nämlich die einzigartige Fähigkeit, Probleme durch Erweiterung der Sprache zu lösen.

-Thom
WIN_20210403_23_50_07_Pro.jpgWIN_20210403_23_50_07_Pro.jpg