Byte-Maschine für das Fort (und nicht nur) in Native American (Teil 3)

Bild

Das Jahr 2019 ist gekommen. Die Neujahrsferien gehen zu Ende. Es ist Zeit, sich an Bytes, Befehle, Variablen, Schleifen zu erinnern ...

Etwas, das ich mit diesen Feiertagen bereits vergessen habe. Muss zusammen erinnern!

Heute werden wir einen Interpreter für unsere Byte-Maschine erstellen. Dies ist der dritte Artikel, die ersten Teile sind hier: Teil 1 , Teil 2 .

Frohes Neues Jahr an alle und willkommen zum Schnitt!

Zunächst werde ich Fragen von fpauk beantworten. Diese Fragen sind absolut richtig. Die Architektur dieser Byte-Maschine ist nun so, dass wir mit direkten Prozessoradressen arbeiten. Im Bytecode sind diese Adressen jedoch nicht, sie werden nach dem Start des Systems gebildet. Nach dem Start des Systems können wir beliebige Zeiger erstellen, und dieser Code funktioniert auf jeder Plattform ordnungsgemäß. Beispielsweise kann die Adresse einer Variablen oder eines Arrays mit dem Befehl var0 abgerufen werden. Dieser Befehl funktioniert auf jeder Plattform und gibt die richtige Adresse zurück, die für diese Plattform spezifisch ist. Dann können Sie mit dieser Adresse arbeiten, wie Sie möchten.

Trotzdem hat fpauk recht. Die Adresse kann nicht im Bytecode gespeichert werden. Es stellt sich heraus, dass wir plattformunabhängigen Code schreiben können, aber dafür müssen wir einige Anstrengungen unternehmen. Stellen Sie insbesondere sicher, dass sich die Adressen nicht im Bytecode befinden. Und sie können beispielsweise eintreten, wenn Sie den kompilierten Code in einer Datei speichern. Es enthält Daten und kann Adressen sein. Zum Beispiel die Werte der Variablen hier, des Kontexts und anderer.

Um ein solches Problem zu beheben, müssen Sie die Adressen virtuell machen. Die Adressierung des x86-Prozessors ist sehr leistungsfähig und fügt in den meisten Fällen nicht einmal zusätzliche Befehle hinzu. Trotzdem werde ich in der aktuellen Architektur mit absoluten Adressen weitermachen. Wenn wir dann zu den Tests kommen, können wir die Adressen in virtuelle Adressen umwandeln und sehen, wie sich dies auf die Leistung auswirkt. Das ist interessant.

Aufwärmen


Und jetzt ein kleines Training. Lassen Sie uns einen weiteren Teil kleiner, aber nützlicher Bytebefehle erstellen. Dies sind die Befehle nip, emit, 1+, + !, - !, Count, Arbeitswörter mit dem Rückgabestapel r>,> r, r @, ein String-Literal (") und konstante Wörter 1, 2, 3, 4, 8. Vergessen Sie nicht, sie in die Befehlstabelle aufzunehmen.

Hier ist der Code für diese Befehle
b_nip = 0x39 bcmd_nip: pop rax mov [rsp], rax jmp _next b_emit = 0x81 bcmd_emit: pop rax mov rsi, offset emit_buf #   mov [rsi], al mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 - stdout mov rdx, 1 #   push r8 syscall #   pop r8 jmp _next b_wp = 0x18 bcmd_wp: incq [rsp] jmp _next b_setp = 0x48 bcmd_setp: pop rcx pop rax add [rcx], rax jmp _next b_setm = 0x49 bcmd_setm: pop rcx pop rax sub [rcx], rax jmp _next b_2r = 0x60 bcmd_2r: pop rax sub rbp, 8 mov [rbp], rax jmp _next b_r2 = 0x61 bcmd_r2: push [rbp] add rbp, 8 jmp _next b_rget = 0x62 bcmd_rget: push [rbp] jmp _next b_str = 0x82 bcmd_str: movzx rax, byte ptr [r8] lea r8, [r8 + rax + 1] jmp _next b_count = 0x84 bcmd_count: pop rcx movzx rax, byte ptr [rcx] inc rcx push rcx push rax jmp _next b_num1 = 0x03 bcmd_num1: push 1 jmp _next b_num2 = 0x04 bcmd_num2: push 2 jmp _next b_num3 = 0x05 bcmd_num3: push 3 jmp _next b_num4 = 0x06 bcmd_num4: push 4 jmp _next b_num8 = 0x07 bcmd_num8: push 8 jmp _next 


Der Befehl nip entfernt das Wort unter dem oberen Rand des Stapels. Dies entspricht dem Austauschen von Drop-Befehlen. Dies kann manchmal hilfreich sein.

Der Befehl emit schiebt ein Zeichen vom Stapel. Es wird dieselbe Systemaufrufnummer 1 verwendet, das Zeichen wird in einem Puffer mit einer Länge von 1 abgelegt.

Der Befehl count ist sehr einfach - er nimmt die Adresse der Zeile mit dem Zähler vom Stapel und wandelt sie in zwei Werte um - die Adresse der Zeile ohne Zähler und die Länge.

Die Befehle b_2r, b_r2, b_rget sind die Fort-Wörter r>,> r, r @. Der erste nimmt das Wort aus dem Rückgabestapel und legt es auf den arithmetischen Stapel. Die zweite führt die entgegengesetzte Operation aus. Der dritte kopiert das Wort vom Rückgabestapel, platziert es in der arithmetischen, der Rückgabestapel ändert sich nicht.

Die Befehle b_setp und b_setm sind die Wörter +! und -! .. Sie nehmen den Wert und die Adresse vom Stapel und ändern das Wort an der angegebenen Adresse, indem sie den Wert zum Stapel hinzufügen oder daraus entfernen.

Der Befehl b_str hat einen Parameter beliebiger Länge - eine Zeile mit einem Zähler. Diese Zeile befindet sich im Bytecode nach dem Befehlsbyte, und der Befehl schiebt einfach die Adresse dieser Zeile auf den Stapel. In der Tat ist dies ein String-Literal.

Der Rest des Teams braucht meiner Meinung nach keine Kommentare.

Wir werden auch einen Befehl zum Drucken einer konstanten Zeichenfolge (. ") Ausgeben. Wir werden ihn wie folgt als Einstiegspunkt für die Eingabe implementieren:

 b_strp = 0x83 bcmd_strp: movsx rax, byte ptr [r8] inc r8 push rax push r8 add r8, rax b_type = 0x80 bcmd_type: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 - stdout pop rdx #   pop rsi #   push r8 syscall #   pop r8 jmp _next 

Dieser Befehl ist ähnlich wie b_str aufgebaut. Nur legt sie nichts auf den Stapel. Die Zeile hinter diesem Befehl als Parameter wird dem Benutzer einfach angezeigt.

Das Aufwärmen ist vorbei, es ist Zeit für etwas Ernsthafteres. Beschäftigen wir uns mit den Wortgeneratoren und anderen var-Befehlen.

Generatorwörter


Erinnern Sie sich an die Variablen. Wir wissen, wie sie auf Bytecode-Ebene angeordnet sind (Befehl var0). Um eine neue Variable zu erstellen, verwendet das Fort die folgende Konstruktion:

 variable < > 

Nach dem Ausführen dieser Sequenz wird ein neues Wort <Variablenname> erstellt. Bei der Ausführung dieses neuen Wortes wird die Adresse auf dem Stapel verschoben, um den Wert der Variablen zu speichern. Es gibt auch Konstanten in der Festung, sie sind wie folgt erstellt:

 <> constant < > 

Nach dem Erstellen der Konstante wird die Ausführung des Wortes <Konstantenname> auf dem Stapel <Wert> platziert.

Somit sind sowohl die Wortvariable als auch die Wortkonstante Generatorwörter. Sie sollen neue Wörter schaffen. In einer Festung werden solche Wörter mit dem Konstrukt create ... does> beschrieben.

Variablen und Konstanten können wie folgt definiert werden:

 : variable create 0 , does> ; : constant create , does> @ ; 

Was bedeutet das alles?

Wenn das Wort create ausgeführt wird, wird ein neues Wort mit dem Namen erstellt, den es beim Ausführen aus dem Eingabestream erhält. Nach der Erstellung wird eine Folge von Wörtern ausgeführt, bevor das Wort> ausführt. Aber im Moment der Ausführung dieses Wortes wird das, was danach geschrieben wird, ausgeführt. Gleichzeitig befindet sich die Datenadresse bereits auf dem Stapel (wie im Fort „Datenfelder“ angegeben).

Beim Erstellen einer Variablen wird also die Sequenz "0" ausgeführt - dies ist die Reservierung eines Maschinenworts mit Nullfüllung. Und wenn das erstellte Wort ausgeführt wird, wird nichts getan (danach tut es nichts). Die Speicheradresse, in der der Wert gespeichert ist, verbleibt einfach auf dem Stapel.

Bei der Definition einer Konstante wird ein Wort mit einem Wert reserviert, der den Stapel ausfüllt. Wenn das erstellte Wort ausgeführt wird, wird "@" ausgeführt, wodurch der Wert an der angegebenen Adresse abgerufen wird.

Lassen Sie uns nun darüber nachdenken, wie das von uns erstellte Wort angeordnet werden kann. Es schiebt die Datenadresse auf den Stapel (wie var0) und überträgt dann die Steuerung an eine bestimmte Adresse, den Bytecode. Der Befehl var0 kehrt sofort zurück. Aber in diesem Fall müssen wir keine Rückkehr machen, sondern tatsächlich einen Übergang.

Ich formuliere noch einmal, was zu tun ist:

  • Datenadresse auf den Stapel legen
  • Springe zu einem Code, nachdem>

Es stellt sich heraus, dass Sie nur die Steuerung an eine andere Bytecode-Adresse übertragen müssen, aber zuerst die Adresse des nächsten Bytes (R8) auf den Stapel legen müssen.

Es ist fast ein Verzweigungsbefehl! Und hier ist sie nicht allein. Habe bereits branch8 und branch16. Wir werden die neuen Befehle var8 und var16 benennen und dies nur die Einstiegspunkte für die Verzweigungsbefehle sein lassen. Wir sparen beim Übergang zum Übergangsteam :) Also wird es so sein:

 b_var8 = 0x29 bcmd_var8: push r8 b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_var16 = 0x30 bcmd_var16: push r8 b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next 

In guter Weise funktioniert der Befehl var32 weiterhin und var64 auch. Wir haben keine so langen Übergänge, da gewöhnliche Übergänge nicht so lang sind. Für den Befehl var ist dies jedoch ein sehr realistischer Fall. Aber im Moment werden wir diese Befehle nicht ausführen. Wir werden es später tun, falls nötig.

Mit den Wortgeneratoren aussortiert. Es war an der Zeit, sich für das Wörterbuch zu entscheiden.

Wortschatz


Wenn sie vereinfacht über das Fort-Wörterbuch sprechen, wird es normalerweise in Form einer unidirektionalen Liste von Wörterbucheinträgen dargestellt. Tatsächlich ist alles etwas komplizierter, da die Festung viele Wörterbücher unterstützt. In der Tat sind sie ein Baum. Die Suche nach einem Wort in einem solchen Baum beginnt mit einem „Blatt“ - dies ist das letzte Wort im aktuellen Wörterbuch. Das aktuelle Wörterbuch wird durch die Kontextvariable definiert, und die Adresse des letzten Wortes befindet sich im Wörterbuchwort. Eine weitere Variable wird zum Verwalten von Wörterbüchern verwendet. Sie definiert ein Wörterbuch, in dem neue Wörter hinzugefügt werden. Somit kann ein Wörterbuch für eine Suche installiert werden und ein anderes, um neue Wörter aufzunehmen.

Für unseren einfachen Fall wäre es möglich, nicht die Unterstützung vieler Wörterbücher zu leisten, aber ich habe beschlossen, nichts zu vereinfachen. Um den Bytecode, die Byte-Maschine, zu verstehen, ist es nicht erforderlich, zu wissen, was in diesem Abschnitt beschrieben wird. Wenn Sie nicht interessiert sind, können Sie diesen Abschnitt einfach überspringen. Nun, wer will die Details wissen - mach weiter!

Zunächst gibt es ein grundlegendes Wörterbuch mit dem Namen. Dies bedeutet, dass es ein solches Wort gibt. Dieses Wort wird auch als "Wörterbuch" bezeichnet, es gibt einige Verwirrung. Wenn es um ein Wort geht, werde ich es daher ein Wörterbuchwort nennen.

Mit dieser Konstruktion werden neue Wörterbücher erstellt:

 vocabulary <  > 

Dadurch wird ein Wort mit dem Namen <Name des erstellten Wörterbuchs> erstellt. Bei der Ausführung legt dieses Wort das erstellte Wörterbuch als Startwörterbuch für die Suche fest.

Tatsächlich enthält das Wörterbuchwort einen Link zum letzten Artikel dieses Wörterbuchs, mit dem die Suche beginnt. Und zum Zeitpunkt der Ausführung schreibt dieses Wörterbuchwort einen Link zu seinem Datenfeld in die Kontextvariable.

Später wird es möglich sein, das Wort Vokabular zu erstellen, das in der aktuellen Implementierung auf dem Fort ganz einfach beschrieben wird:

 : vocabulary create context @ , does> context ! ; 

Also, erschaffe das Wort weiter. Wir werden den Befehl var8 verwenden. Bytecode "Kontext!" Platz direkt hinter dem Datenfeld:

 forth: .byte b_var8 .byte does_voc - . - 1 .quad 0 # <--      .      ,    -    . does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit 

Nun zurück zum Erstellen des Wörterbuchs.

Im Allgemeinen wird in einer Festung eine Beschreibung eines Wortes im Speicher als „Wörterbucheintrag“ bezeichnet. Normalerweise würde ich sagen, dass es einen Artikeltitel und seinen Code gibt. In einer Festung ist jedoch nicht alles üblich. Dort wird es als "Namensfeld", "Kommunikationsfeld", "Codefeld" und "Datenfeld" bezeichnet. Ich werde versuchen, Ihnen zu sagen, was dies alles auf traditionelle Weise bedeutet.

Das Namensfeld ist der Name des Wortes "Zeile mit einem Zähler". Es ist wie im alten Pascal - Byte der Stringlänge, dann String. Das Linkfeld ist ein Link zum vorherigen Artikel. Früher gab es nur eine Adresse, aber wir werden einen plattformunabhängigen Code haben, und dies wird ein Offset sein. Das Codefeld, traditionell in der Festung, ist Maschinencode (wenn sich die Implementierung in einer direkten Linie befindet). Für Wörter außerhalb des Kernels wurde _call aufgerufen. Wir werden nur einen Bytecode haben. Und das Datenfeld ist für Wörter, die Daten enthalten - zum Beispiel für Variablen oder Konstanten. Das Wort Wörterbuch bezieht sich übrigens auch darauf.

Für den Compiler benötigen wir noch Flags. Normalerweise benötigt eine Festung nur eine Flagge - sofort, und sie befindet sich in einem langen Byte (manchmal gibt es eine andere - versteckt). Dies gilt jedoch für direkt genähten Code, bei dem die Prozessorsteuerung beim Aufruf in das Codefeld übertragen wird. Und wir haben verschiedene Wörter - Bytecode und Maschinencode, und mindestens zwei oder sogar drei Flags werden benötigt.

Wie viel wird für das Kommunikationsfeld benötigt? Am Anfang wollte ich 16 Bit verwenden. Dies ist ein Link zum vorherigen Wort, und das Wort ist definitiv kleiner als 64 KB. Aber dann erinnerte ich mich, dass das Wort Daten fast jeder Größe enthalten kann. Außerdem kann der Link in Gegenwart mehrerer Wörterbücher viele Wörter umfassen. Es stellt sich heraus, dass in den meisten Fällen 8 Bit ausreichen, aber es können 16 und 32 sein. Und sogar 64 Bit, wenn Daten mit mehr als 4 GB vorhanden sind. Lassen Sie uns alle Optionen unterstützen. Welche Option wird verwendet - setzen Sie die Flags. Es werden mindestens 4 Flags ausgegeben: das unmittelbare Attribut, das Kernwortattribut und 2 Bits pro Variante des verwendeten Kommunikationsfelds. Auf keine andere Weise muss ein separates Byte für Flags verwendet werden.

Wir definieren die Flags wie folgt:

 f_code = 0x80 f_immediate = 0x60 

Das f_code-Flag steht für Kernelwörter, die in Assembler geschrieben wurden. Das f_immediate-Flag ist für den Compiler im nächsten Artikel hilfreich. Und die zwei niedrigstwertigen Bits bestimmen die Länge des Kommunikationsfeldes (1, 2, 4 oder 8 Bytes).

Der Titel des Artikels lautet also wie folgt:

  • Flags (1 Byte)
  • Kommunikationsfeld (1-8 Bytes)
  • Name Länge Byte
  • Name (1-255 Bytes)

Bis zu diesem Punkt habe ich die Funktionen des "Makro" -Assemblers nicht verwendet. Und jetzt brauchen wir sie. So habe ich ein Makro mit dem Namenselement erhalten, um den Titel des Wortes zu bilden:

 .macro item name, flags = 0 link = . - p_item 9: .if link >= -256/2 && link < 256/2 .byte \flags .byte link .elseif link >= -256*256/2 && link < 256*256/2 .byte \flags | 1 .word . - p_item .elseif link >= -256*256*256*256/2 && link < 256*256*256*256/2 .byte \flags | 2 .int . - p_item .elseif link >= -256*256*256*256*256*256*256*256/2 && link < 256*256*256*256*256*256*256*256/2 .byte \flags | 3 .quad . - p_item .endif p_item = 9b .byte 9f - . - 1 .ascii "\name" 9: .endm 

Dieses Makro verwendet den Wert p_item - dies ist die Adresse des vorherigen Wörterbucheintrags. Dieser Wert am Ende wird für die zukünftige Verwendung aktualisiert: p_item = 9b. Hier ist 9b eine Bezeichnung, keine Zahl, nicht verwirren :) Das Makro hat zwei Parameter - den Namen des Wortes und Flags (optional). Zu Beginn des Makros wird der Versatz zum vorherigen Wort berechnet. Dann werden abhängig von der Größe des Versatzes die Flags und das Kommunikationsfeld der gewünschten Größe kompiliert. Dann das Byte der Länge des Namens und des Namens selbst.

Definieren Sie vor dem ersten Wort p_item wie folgt:

 p_item = . 

Der Punkt ist die aktuelle Kompilierungsadresse im Assembler. Aufgrund dieser Definition bezieht sich das erste Wort auf sich selbst (das Kommunikationsfeld ist 0). Dies ist ein Zeichen für das Ende der Wörterbücher.

Was steht übrigens im Codefeld der Kernelwörter? Sie müssen den Befehlscode mindestens irgendwo speichern. Ich beschloss, den einfachsten Weg zu gehen. Für Kernelwörter gibt es auch einen Bytecode. Für die meisten Teams ist dies nur ein Bytebefehl, gefolgt von b_exit. Daher muss für den Interpreter das Flag f_code nicht analysiert werden, und die Befehle dafür unterscheiden sich in keiner Weise. Sie müssen nur den Bytecode für alle aufrufen.

Diese Option bietet einen weiteren Vorteil. Für Befehle mit Parametern können Sie sichere Parameter angeben. Wenn Sie beispielsweise den Befehl lit in Fort-Implementierungen mit direkt genähtem Code aufrufen, stürzt das System ab. Und hier wird es dort geschrieben, zum Beispiel leuchtet 0, und diese Sequenz setzt einfach 0 auf den Stapel. Auch für die Verzweigung kann sicher gearbeitet werden!

  .byte branch8 .byte 0f - . 0: .byte b_exit 

Bei einem solchen Anruf entsteht ein gewisser Aufwand, der für den Dolmetscher jedoch nicht von Bedeutung ist. Der Compiler analysiert die Flags und kompiliert den richtigen und schnellen Code.

Das erste Wort wird natürlich das Wort „her“ sein - das Grundvokabular, das wir erstellen. Hier kommen Sie einfach in den praktischen var-Befehl mit einem Link zum Code nach does>. Ich habe diesen Code bereits im vorherigen Abschnitt zitiert, werde ihn aber mit der Überschrift noch einmal wiederholen:

 p_item = . item forth .byte b_var8 .byte does_voc - . - 1 .quad 0 does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit 

Und wir werden sofort die Kontextvariablen erstellen und sie benötigen, um nach Wörtern zu suchen:

  item .byte b_var0 .quad 0 item context context: .byte b_var0 .quad 0 

Und jetzt müssen Sie geduldig sein und einen Titel für jedes Wort schreiben, das wir in Assembler mit dem Flag f_code geschrieben haben:

  item 0, f_code .byte b_num0 .byte b_exit item 1, f_code .byte b_num1 .byte b_exit ... item 1-, f_code .byte b_wm .byte b_exit item 1+, f_code .byte b_wp .byte b_exit item +, f_code .byte b_add .byte b_exit item -, f_code .byte b_sub .byte b_exit item *, f_code .byte b_mul .byte b_exit 

Usw…

Mit in Bytecode geschriebenen Teams ist das noch einfacher. Es reicht aus, nur eine Überschrift vor dem Bytecode einzufügen, genau wie das vierte Wort, zum Beispiel:

  item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint ... 

Für Befehle mit Parametern erstellen wir sichere Parameter. Lassen Sie zum Beispiel die Lite-Befehle die Nummer Pi zurückgeben. Wenn jemand sie interaktiv aufruft, gibt es so ein Osterfest :)

  item lit8, f_code .byte b_lit8 .byte 31 .byte b_exit item lit16, f_code .byte b_lit16 .word 31415 .byte b_exit item lit32, f_code .byte b_lit32 .int 31415926 .byte b_exit item lit64, f_code .byte b_lit64 .quad 31415926535 .byte b_exit 

Das letzte Wort in der Liste bringt das Wort symbolisch zum Abschied. Wir müssen jedoch noch die Adresse dieses Wortes im Datenfeld weiter initialisieren. Verwenden Sie den Befehl var0, um die Adresse dieses Wortes abzurufen:

 last_item: .byte b_var0 item bye, f_code .byte b_bye 

Wenn wir in diesem Entwurf die Adresse last_item im Bytecode aufrufen, erhalten wir die Adresse des Wortes bye. Um es in die Datenfelder des vierten Wortes zu schreiben, führen Sie es aus, und die gewünschte Adresse befindet sich im Kontext. Der Systeminitialisierungscode sieht also folgendermaßen aus:

 forth last_item context @ ! 

Und jetzt gehen wir direkt zum Dolmetscher. Zuerst müssen wir mit dem Eingabepuffer arbeiten und Wörter daraus extrahieren. Ich möchte Sie daran erinnern, dass der Dolmetscher in der Festung sehr einfach ist. Er extrahiert nacheinander Wörter aus dem Eingabepuffer und versucht, sie zu finden. Wenn das Wort gefunden wird, startet der Interpreter es zur Ausführung.

Eingabepuffer und Wortextraktion


Um ehrlich zu sein, möchte ich nicht viel Zeit damit verbringen, die Standards der Festung zu studieren. Trotzdem werde ich versuchen, es ihnen so nahe wie möglich zu bringen, hauptsächlich aus dem Gedächtnis. Wenn Fort-Experten hier eine starke Diskrepanz feststellen - schreiben Sie, ich werde sie beheben.

Das Fort verfügt über drei Variablen für die Arbeit mit dem Puffer: tib, #tib und> in. Die tib-Variable überträgt die Adresse des Eingabepuffers auf den Stapel. Die Variable #tib schiebt die Anzahl der Zeichen, die sich im Puffer befinden, auf den Stapel. Und die Variable> in enthält den Offset im Eingabepuffer, hinter dem sich der Rohtext befindet. Definieren Sie diese Variablen.

  item tib .byte b_var0 v_tib: .quad 0 item #tib .byte b_var0 v_ntib: .quad 0 item >in .byte b_var0 v_in: .quad 0 

Als nächstes machen wir das Wort blword. Dieses Wort erhält unter Verwendung der angegebenen Variablen das nächste Wort aus dem Eingabestream. Ein Leerzeichen wird als Trennzeichen und alle Zeichen mit einem Code verwendet, der kleiner als ein Leerzeichen ist. Dieses Wort wird im Assembler sein. Nach dem Debuggen stellte sich Folgendes heraus:

 b_blword = 0xF0 bcmd_blword: mov rsi, v_tib #    mov rdx, rsi #   RDX       mov rax, v_in #     mov rcx, v_ntib #    add rsi, rax #  RSI -      sub rcx, rax #     jz 3f word2: lodsb #   AL  RSI   cmp al, ' ' ja 1f #    (    ) dec rcx jnz word2 #    3: sub rsi, rdx mov v_in, rsi push rcx jmp _next 1: lea rdi, [rsi - 1] # RDI = RSI - 1 ( ) dec rcx word3: lodsb cmp al, ' ' jbe 2f dec rcx jnz word3 2: mov rax, rsi sub rsi, rdx #        (   ) mov v_in, rsi sub rax, rdi dec rax jz word1 push rdi #   word1: push rax #   jmp _next 

Dieses Wort ähnelt dem Standardwort, berücksichtigt jedoch im Gegensatz dazu alle Trennzeichen und kopiert das Wort nicht in den Puffer. Es werden nur zwei Werte auf dem Stapel zurückgegeben - Adresse und Länge. Wenn das Wort nicht abgerufen werden kann, wird 0 zurückgegeben. Es ist an der Zeit, mit dem Schreiben des Interpreters zu beginnen.

Wortsuche und Dolmetscher


Lassen Sie uns zunächst das Wort interpretieren. Dieses Wort wählt mit blworld ein neues Wort aus dem Puffer aus, sucht es im Wörterbuch und führt es aus. Und so wiederholt es sich, bis der Puffer erschöpft ist. Wir haben immer noch nicht die Möglichkeit, nach einem Wort zu suchen, daher schreiben wir einen Teststub, der das Wort einfach mit dem Typ aus dem Puffer druckt. Dies gibt uns die Möglichkeit, blworld zu überprüfen und zu debuggen:

 # : interpret begin blword dup while type repeat drop ; item interpret 1: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_type .byte b_branch8 .byte 1b - . 0: .byte b_drop .byte b_exit 

Lassen Sie jetzt das Wort aufhören. Normalerweise tun sie dies bei der Implementierung von Fort-Systemen: Sie verwenden das Wort Quit oder Abort, um in den Interpreter-Modus zu gelangen. Das Wort quit spült Stapel und startet eine Endlosschleife der Puffereingabe und -interpretation. Bei uns ist es nur ein Aufruf zur Interpretation. Der Code für dieses Wort besteht aus zwei Teilen. Der erste Teil befindet sich im Assembler, der zweite Teil im Bytecode. Der erste Teil:

 b_quit = 0xF1 bcmd_quit: lea r8, quit mov sp, init_stack mov bp, init_rstack jmp _next 

Der zweite Teil:

 quit: .byte b_call16 .word interpret - . - 2 .byte b_bye 

Wie üblich befindet sich der Assembler-Code im Textabschnitt, der Bytecode im Datenabschnitt.

Und schließlich ändern Sie den Startbytecode. Das Wörterbuch wird nur initialisiert, ein Puffer in der Startzeile gesetzt und quit beendet.

 # forth last_item context @ ! start_code tib ! <  > #tib ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_call8 .byte start_code - . - 1 .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .world 1f - 0f .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_quit start_code: .byte b_var0 0: .ascii "word1 word2 word3" 1: 

Kompilieren, verknüpfen, ausführen!

 $ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth word1word2wordBye! 

Es ist ein bisschen wie Haferbrei, aber genau das sollte das Ergebnis sein. Wir geben ohne Trennzeichen aus. Übrigens, setzen Sie den Zeilenvorschub vor dem Kauf für die Zukunft, dies wird nicht schaden.

Natürlich musste ich am Debuggen basteln. Zusätzlich zu dem bereits erwähnten „Segmentierungsfehler (Core Dumped)“ wurden manchmal interessante Ergebnisse erzielt. Zum Beispiel:

 $ ./forth word1word2word3forth)%60Acurrent(context(%600lit8lit16zlit32v%5E%DF%80lit64v%5E%DF%80call8call16call32branch8branch16qbranch8qbranch16exit1-+!-%22*#/$mod%25/mod&abs'dup0drop1swap2rot3-rot4over5pick6roll7depth8@@!Ac@Bc!Cw@Dw!Ei@Fi!G0=P0%3CQ0%3ER=S%3CT%3EU%3C=V%3E=Wvar8)var160base(holdbuf(Qholdpoint(hold@0U110ACp@&20T0!?!%3CgF!A0@RF!5%220'%DE%A61Q-%DD%80:tib(%7F%60(%3Ein(%20%20%20%20%20%20%20interpret01('byeSegmentation%20fault%20(core%20dumped) 

Dies scheint unser gesamtes binäres Wörterbuch mit in Trennzeichen geschnittenem Text zu sein :) Es geschah, als ich "dec rcx" vor word3 im Befehl b_blword vergaß.

Wir können Wörter aus dem Eingabestream auswählen, es gibt ein Wörterbuch. Jetzt müssen Sie eine Wörterbuchsuche implementieren und Wörter zur Ausführung starten. Dies erfordert die Wörter find, cfa und execute.

Die Wortsuche nimmt die Adresse des Wortes und seine Länge vom Stapel. Dieses Wort wird von der Adresse des Wörterbucheintrags oder von 0 zurückgegeben, wenn es nicht gefunden wird.

Das Wort cfa an der Artikeladresse berechnet die Adresse des ausführbaren Bytecodes.

Und das Wort execute führt den Bytecode aus.

Beginnen wir mit find. In Fort-Standards wird eine Adresse benötigt - eine Zeile mit einem Zähler. Aber ich möchte den String nicht noch einmal in den Puffer kopieren, deshalb werde ich ein wenig von den Standards abweichen. Die Wortsuche nimmt zwei Parameter auf dem Stapel an - die Adresse und die Länge der Zeichenfolge (die tatsächlich das Wort blword zurückgibt). Nach dem Debuggen nahm dieses Wort die folgende Form an:

 b_find = 0xF2 bcmd_find: pop rbx #   pop r9 #   mov rdx, v_context mov rdx, [rdx] #        #   find0: mov al, [rdx] #  and al, 3 #   -     ,     ,    or al, al jz find_l8 cmp al, 1 jz find_l16 cmp al, 2 jz find_l32 mov r10, [rdx + 1] #  64  lea rsi, [rdx + 9] #   jmp find1 find_l32: movsx r10, dword ptr [rdx + 1] #  32  lea rsi, [rdx + 5] #   jmp find1 find_l16: movsx r10, word ptr [rdx + 1] #  16  lea rsi, [rdx + 3] #   jmp find1 find_l8: movsx r10, byte ptr [rdx + 1] #  8  lea rsi, [rdx + 2] #   find1: movzx rax, byte ptr [rsi] #       cmp rax, rbx jz find2 #      find3: or r10, r10 jz find_notfound #  ,    add rdx, r10 #     jmp find0 #  ,   find2: inc rsi mov rdi, r9 mov rcx, rax repz cmpsb jnz find3 #   push rdx jmp _next find_notfound: push r10 jmp _next 

Vielleicht ist dies das schwierigste Wort für heute. Jetzt ändern wir das Wort interpret und ersetzen den Typ durch "find":

 # : interpret begin blword dup while find . repeat drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_find .byte b_call16 .word dot - . - 2 .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit 

In die Testzeile müssen Sie die Wörter einfügen, die sich im Wörterbuch befinden, z. B. "0 1 - dup +.".

Alles ist startbereit!

 $ ld forth.o -o forth $ ./forth 6297733 6297898 6298375 Bye! 

Großartig, die Suche funktioniert. Dies sind die Adressen von Wörtern (dezimal). Nun das Wort cfa. Lassen Sie es auch in Assembler sein, es ist sehr einfach, das Arbeiten mit Flags ist ähnlich zu finden:

 b_cfa = 0xF3 bcmd_cfa: pop rdx #    mov al, [rdx] #  and al, 3 #   -     ,     ,    or al, al jz cfa_l8 cmp al, 1 jz cfa_l16 cmp al, 2 jz cfa_l32 lea rsi, [rdx + 9] #   (64  ) jmp cfa1 find_l32: lea rsi, [rdx + 5] #   (32  ) jmp cfa1 find_l16: lea rsi, [rdx + 3] #   (16  ) jmp cfa1 find_l8: lea rsi, [rdx + 2] #   (8  ) xor rax, rax lodsb add rsi, rax push rsi jmp _next 

Und schließlich ist das Wort ausführen noch einfacher:

 b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 #       pop r8 #  - jmp _next 

Korrigieren Sie das Wort interpretieren und ausführen!

 # : interpret begin blword dup while find cfa execute repeat drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_find .byte b_cfa .byte b_execute .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit 

Start:

 $ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth -2 Bye! 

Urrra, verdient! (C) Cat Matroskin

Wenn Sie 1 von 0 subtrahieren und das Ergebnis zu sich selbst addieren, ist es -2 :)
Das ist großartig, aber ich möchte die Befehle trotzdem über die Tastatur eingeben. Und es gibt noch ein weiteres Problem: Unser Interpreter versteht nur die Zahlen 0, 1, 2, 3, 4 und 8 (die als Konstanten definiert sind). Was würde er lernen, irgendwelche Zahlen zu verstehen, brauchen Sie das Wort "Zahl"? Auf die gleiche Weise wie für das Wort find werde ich den Puffer nicht verwenden. Das Wort "Nummer?" nimmt zwei Parameter auf dem Stapel - die Adresse des Strings und die Länge. Bei Erfolg werden die empfangene Nummer und das Flag 1 zurückgegeben. Wenn die Konvertierung nicht erfolgreich ist, befindet sich eine Nummer auf dem Stapel: 0.

Der Code erwies sich als lang, aber ziemlich einfach und linear:

 b_number = 0xF5 bcmd_number: pop rcx #   pop rsi #  xor rax, rax #   xor rbx, rbx #     mov r9, v_base #  xor r10, r10 #   or rcx, rcx jz num_false mov bl, [rsi] cmp bl, '+' jnz 1f inc rsi dec rcx jz num_false jmp num0 1: cmp bl, '-' jnz num0 mov r10, 1 inc rsi dec rcx jz num_false num0: mov bl, [rsi] cmp bl, '0' ja num_false cmp bl, '9' jae num_09 cmp bl, 'A' ja num_false cmp bl, 'Z' jae num_AZ cmp bl, 'a' ja num_false sub bl, 'a' - 10 jmp num_check num_AZ: sub bl, 'A' - 10 jmp num_check num_09: sub bl, '0' num_check: cmp rbx, r9 jge num_false add rax, rbx mul r9 inc rsi dec rcx jnz num0 or r10, r10 push rax push 1 jmp _next num_false: xor rcx, rcx push rcx jmp _next 

Interpretieren ändern. Wenn das Wort nicht im Wörterbuch enthalten ist, werden wir versuchen, es als Zahl zu interpretieren:

 # : interpret # begin # blword dup # while # over over find dup # if -rot drop drop cfa execute else number? drop then # repeat # drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_numberq .byte b_drop 2: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit last_item: .byte b_var0 item bye, f_code .byte b_bye 

Und hier habe ich! Debuggen Sie einen solchen Bytecode im Assembler, ohne Haltepunkte im Bytecode, ohne die Möglichkeit, einfach entlang des Bytecodes zu "schreiten" ... Außerdem mit nicht den einfachsten Bewegungen auf dem Stapel und ohne die einfache Möglichkeit, den Inhalt des Stapels anzuzeigen ... und auf GDB, wo Nur die Kommandozeile ... Ich sage es dir - es ist nur eine Gehirnexplosion! Nicht schlimmer. Dies ist eine Gehirnexplosion !

Aber ... wir sind Inder, wir werden immer Problemumgehungen finden :)

Im Allgemeinen habe ich diese Lösung gefunden: Ich habe einen Befehl implementiert, um den Inhalt des Stapels anzuzeigen - "s". Der Befehl ist nicht der einfachste, aber dennoch einfacher zu interpretieren. Und wie sich herausstellte , ochchchen nützlich. Da ist sie:

 # : .s depth dup . c": emit do dup while dup pick . 1- again drop ; item .s # 11 22 33 prstack: .byte b_depth # 11 22 33 3 .byte b_dup # 11 22 33 3 3 .byte b_lit8 .byte '(' .byte b_emit .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_strp # 11 22 33 3 .byte 3 .ascii "): " 1: .byte b_dup # 11 22 33 3 3 .byte b_qnbranch8 # 11 22 33 3 .byte 2f - . .byte b_dup # 11 22 33 3 3 .byte b_pick # 11 22 33 3 11 .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_wm # 11 22 33 2 .byte b_branch8 .byte 1b - . 2: .byte b_drop # 11 22 33 .byte b_exit 

Rechts habe ich nach der Ausführung jedes Befehls ein Beispiel für den Inhalt des Stapels gegeben. Natürlich gibt es einen Zyklus, und dies ist nur der erste Durchgang. Der Rest ist jedoch sehr ähnlich, nur der Wert oben im Stapel ändert sich. Nach einer solchen "Spur" hat das Team sofort verdient!

Zum Debuggen habe ich folgende Makros erstellt:

 .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm 

Wird durch Einfügen an den richtigen Stellen auf folgende Weise verwendet:

  item interpret interpret: .byte b_blword prs .byte b_dup prs .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over ...... 

Infolgedessen ergab der erste Start die folgende Ausgabe:

 $ ./forth (2 ): 6297664 1 (3 ): 6297664 1 1 (3 ): 2 6297666 1 (4 ): 2 6297666 1 1 (4 ): 2 3 6297668 1 (5 ): 2 3 6297668 1 1 (3 ): 6 6297670 2 (4 ): 6 6297670 2 2 (4 ): 6 6297670 6297673 1 (5 ): 6 6297670 6297673 1 1 6297670 (2 ): 6 0 (3 ): 6 0 0 Bye! 

Jede Bewegung auf dem Stapel ist deutlich zu sehen. Es war notwendig, dies früher zu tun :)

Ich ging weiter, indem ich ein weiteres Debug-Makro erstellte:

 .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm 

Infolgedessen wurde es möglich, dies zu tun:

  item interpret interpret: .byte b_blword pr blworld prs .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over prs .byte b_find pr find prs .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa pr execute prs .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_numberq pr numberq prs .byte b_drop 2: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit 

Und hol dir das:

 $ ./forth blworld(2 ): 6297664 2 (4 ): 6297664 2 6297664 2 find(3 ): 6297664 2 0 numberq(2 ): 6297664 0 blworld(3 ): 6297664 6297667 2 (5 ): 6297664 6297667 2 6297667 2 find(4 ): 6297664 6297667 2 0 numberq(3 ): 6297664 6297667 0 blworld(4 ): 6297664 6297667 6297670 1 (6 ): 6297664 6297667 6297670 1 6297670 1 find(5 ): 6297664 6297667 6297670 1 6297958 execute(3 ): 6297664 6297667 6297962 blworld(3 ): 39660590749888 6297672 1 (5 ): 39660590749888 6297672 1 6297672 1 find(4 ): 39660590749888 6297672 1 6298496 execute(2 ): 39660590749888 6298500 39660590749888 blworld(1 ): 0 Bye! 

Es war ein Versuch, die Zeichenfolge "20 30 *" zu interpretieren.

Und Sie können die Quellzeilennummern anzeigen ... okay, vielleicht dann ...

Natürlich ist dies eine klassische Protokollierungstechnik zum Debuggen, aber etwas, an das ich mich nicht sofort erinnerte.

Im Allgemeinen habe ich beim Debuggen einen Stapel gefunden, der ins Ausland geht. Dies ist das Gegenteil von Überlauf, wenn sie versuchen, mehr zu nehmen, als sie setzen. Fügte ihre Kontrolle zu ".s" hinzu.
Mit Hilfe neuer Makros war das Debuggen schnell. Vorher habe ich übrigens einen Bytecode pro Zeile gepostet. Mit Assembler können Sie jedoch mehrere Bytes in eine Zeichenfolge einfügen. Warum nicht?

Lassen Sie uns die Wortinterpretation mit zwei Überprüfungen abschließen: dass das Wort nicht in eine Zahl konvertiert wurde, und um den Stapel im Ausland zu verlassen. Infolgedessen ist die Interpretation wie folgt:

  item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # ,    .byte b_qbranch8, 3f - . #     0, ,      3 .byte b_type #    .byte b_strp #   .byte 19 #     .ascii " : word not found!\n" .byte b_quit #    3: .byte b_nip, b_nip #  ,     ( b_over, b_over) 2: #       .byte b_depth #    .byte b_zlt # ,   0 ( 0<) .byte b_qnbranch8, interpret_ok - . #   ,    ,   .byte b_strp #    .byte 14 .ascii "\nstack fault!\n" .byte b_quit #    interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit 

Übrigens ist es erwähnenswert, dass der Befehl quit jetzt Stapel löscht und die Interpretation erneut startet, ohne den Status des Puffers zu ändern. Somit geht die Interpretation weiter, jedoch mit „frischen“ Stapeln. Wir werden das etwas später beheben.

Sie müssen nur noch die Tastatureingabe organisieren.

Tastatureingabe


Die Tastatureingabe im Fort ist einfach. Es gibt das Wort erwarten, es braucht zwei Parameter - die Adresse des Puffers und seine Größe. Dieses Wort führt die Tastatureingabe durch. Die tatsächlich eingegebene Anzahl von Zeichen wird in die Bereichsvariable eingefügt. Lassen Sie uns diese Worte machen. Wir werden von der Standardeingabe eingeben.

 .data item span span: .byte b_var0 v_span: .quad 0 .text b_expect = 0x88 bcmd_expect: mov rax, 0 #   № 1 - sys_read mov rdi, 0 #  № 1 - stdout pop rdx #   pop rsi #   push r8 syscall #   pop r8 mov rbx, rax or rax, rax jge 1f xor rbx, rbx 1: mov v_span, rbx jmp _next 

Jetzt müssen wir einen Tastatureingabepuffer erstellen. Lassen Sie es 256 Zeichen lang sein.
Machen wir es anstelle der vorherigen Testlinie.

 inbuf_size = 256 inbuf: .byte b_var0 .space inbuf_size 

Und wir ändern das Beenden sowie den Startbytecode. Setzen Sie die tib-Variable auf den Inbuf-Eingabepuffer, rufen Sie Expect auf und kopieren Sie den Wert von span nach #tib. Die Variable> in wird annulliert, wir nennen interpret. Und so wiederholen wir in einem Zyklus. Es gibt Kugeln - um eine Eingabeaufforderung hinzuzufügen, und es wäre schön, den Status des Stapels anzuzeigen (und wir haben bereits einen vorgefertigten Befehl dafür!). Nach mehreren Iterationen haben wir den folgenden Code erhalten (Befehl start and quit):

 # forth last_item context @ ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_quit inbuf: .byte b_var0 .space inbuf_size # begin inbuf dup tib ! inbuf_size expect span @ #tib ! 0 >in ! interpret again quit: .byte b_strp, 1 .ascii "\n" .byte b_call16 .word prstack - . - 2 .byte b_strp .byte 2 .ascii "> " .byte b_call16 .word inbuf - . - 2 .byte b_dup .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word inbuf_size .byte b_expect .byte b_call16 .word span - . - 2 .byte b_get .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_num0 .byte b_call16 .word bin - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_branch8, quit - . 

Und hier ist das Ergebnis:

 $ ./forth ( 0 ): > 60 ( 1 ): 60 > 60 24 ( 3 ): 60 60 24 > rot ( 3 ): 60 24 60 > -rot ( 3 ): 60 60 24 > swap ( 3 ): 60 24 60 > * * . 86400 ( 0 ): > 200 30 /mod ( 2 ): 20 6 > bye Bye! $ 

Alles nach dem Symbol ">" ist meine Tastatureingabe. Der Rest ist die Antwort des Systems. Ich spielte ein wenig mit Befehlen und tippte über die Tastatur. Er führte mehrere Stapeloperationen durch und berechnete die Anzahl der Sekunden in Tagen.

Zusammenfassung


Der Dolmetscher ist vollständig und arbeitet. Und verabschiedet sich höflich - von ihm "Tschüss" und er "Tschüss" :)
Als Einladung - der Inhalt des Rechenstapels. Die erste Zahl in Klammern ist die Größe des Stapels, dann der Inhalt und die Eingabeaufforderung zur Eingabe von ">". Sie können beliebige implementierte Befehle eingeben (ich habe 76 Befehle gezählt). Richtig, viele sind nur für den Compiler sinnvoll - zum Beispiel Literale, Übergänge, Aufrufbefehle.

Vollständige Quelle (ca. 1300 Zeilen)
 .intel_syntax noprefix stack_size = 1024 f_code = 0x80 f_immediate = 0x60 .macro item name, flags = 0 link = p_item - . 9: .if link >= -256/2 && link < 256/2 .byte \flags .byte link .elseif link >= -256*256/2 && link < 256*256/2 .byte \flags | 1 .word link .elseif link >= -256*256*256*256/2 && link < 256*256*256*256/2 .byte \flags | 2 .int link .elseif link >= -256*256*256*256*256*256*256*256/2 && link < 256*256*256*256*256*256*256*256/2 .byte \flags | 3 .quad link .endif p_item = 9b .byte 9f - . - 1 .ascii "\name" 9: .endm .section .data init_stack: .quad 0 init_rstack: .quad 0 emit_buf: .byte 0 inbuf_size = 256 msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte #  len    msg_bye: .ascii "\nBye!\n" msg_bye_len = . - msg_bye bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_num1, bcmd_num2, bcmd_num3, bcmd_num4, bcmd_num8 # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_call8, bcmd_call16, bcmd_call32, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_qnbranch8, bcmd_qnbranch16,bcmd_bad, bcmd_exit # 0x10 .quad bcmd_wp, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_add, bcmd_sub, bcmd_mul, bcmd_div, bcmd_mod, bcmd_divmod, bcmd_abs # 0x20 .quad bcmd_var0, bcmd_var8, bcmd_var16, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_dup, bcmd_drop, bcmd_swap, bcmd_rot, bcmd_mrot, bcmd_over, bcmd_pick, bcmd_roll # 0x30 .quad bcmd_depth, bcmd_nip, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_get, bcmd_set, bcmd_get8, bcmd_set8, bcmd_get16, bcmd_set16, bcmd_get32, bcmd_set32 # 0x40 .quad bcmd_setp, bcmd_setm, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_zeq, bcmd_zlt, bcmd_zgt, bcmd_eq, bcmd_lt, bcmd_gt, bcmd_lteq, bcmd_gteq # 0x50 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_2r, bcmd_r2, bcmd_rget, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_emit, bcmd_str, bcmd_strp, bcmd_count, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_expect, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x90 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_blword, bcmd_quit, bcmd_find, bcmd_cfa, bcmd_execute, bcmd_numberq, bcmd_bad, bcmd_bad # 0xF0 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # forth last_item context @ ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_quit inbuf: .byte b_var0 .space inbuf_size # begin inbuf dup tib ! inbuf_size expect span @ #tib ! 0 >in ! interpret again quit: .byte b_strp, 1 .ascii "\n" .byte b_call16 .word prstack - . - 2 .byte b_strp .byte 2 .ascii "> " .byte b_call16 .word inbuf - . - 2 .byte b_dup .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word inbuf_size .byte b_expect .byte b_call16 .word span - . - 2 .byte b_get .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_num0 .byte b_call16 .word bin - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_branch8, quit - . p_item = . item forth forth: .byte b_var8 .byte does_voc - . .quad 0 does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit item current .byte b_var0 .quad 0 item context context: .byte b_var0 v_context: .quad 0 item 0, f_code .byte b_num0 .byte b_exit item 1, f_code .byte b_num1 .byte b_exit item 2, f_code .byte b_num2 .byte b_exit item 3, f_code .byte b_num3 .byte b_exit item 4, f_code .byte b_num4 .byte b_exit item 8, f_code .byte b_num8 .byte b_exit item lit8, f_code .byte b_lit8 .byte 31 .byte b_exit item lit16, f_code .byte b_lit16 .word 31415 .byte b_exit item lit32, f_code .byte b_lit32 .int 31415926 .byte b_exit item lit64, f_code .byte b_lit64 .quad 31415926 .byte b_exit item call8, f_code .byte b_call8 .byte 0f - . - 1 0: .byte b_exit item call16, f_code .byte b_call16 .word 0f - . - 2 0: .byte b_exit item call32, f_code .byte b_call32 .int 0f - . - 4 0: .byte b_exit item branch8, f_code .byte b_branch8 .byte 0f - . 0: .byte b_exit item branch16, f_code .byte b_branch16 .word 0f - . 0: .byte b_exit item qbranch8, f_code .byte b_qbranch8 .byte 0f - . 0: .byte b_exit item qbranch16, f_code .byte b_qbranch16 .word 0f - . 0: .byte b_exit item exit, f_code .byte b_exit item 1-, f_code .byte b_wm .byte b_exit item 1+, f_code .byte b_wp .byte b_exit item +, f_code .byte b_add .byte b_exit item -, f_code .byte b_sub .byte b_exit item *, f_code .byte b_mul .byte b_exit item /, f_code .byte b_div .byte b_exit item mod, f_code .byte b_mod .byte b_exit item /mod, f_code .byte b_divmod .byte b_exit item abs, f_code .byte b_abs .byte b_exit item dup, f_code .byte b_dup .byte b_exit item drop, f_code .byte b_drop .byte b_exit item swap, f_code .byte b_swap .byte b_exit item rot, f_code .byte b_rot .byte b_exit item -rot, f_code .byte b_mrot .byte b_exit item over, f_code .byte b_over .byte b_exit item pick, f_code .byte b_pick .byte b_exit item roll, f_code .byte b_roll .byte b_exit item depth, f_code .byte b_depth .byte b_exit item @, f_code .byte b_get .byte b_exit item !, f_code .byte b_set .byte b_exit item c@, f_code .byte b_get8 .byte b_exit item c!, f_code .byte b_set8 .byte b_exit item w@, f_code .byte b_get16 .byte b_exit item w!, f_code .byte b_set16 .byte b_exit item i@, f_code .byte b_get32 .byte b_exit item i!, f_code .byte b_set32 .byte b_exit item +!, f_code .byte b_setp .byte b_exit item -!, f_code .byte b_setm .byte b_exit item >r, f_code .byte b_2r .byte b_exit item r>, f_code .byte b_r2 .byte b_exit item r@, f_code .byte b_rget .byte b_exit item "0=", f_code .byte b_zeq .byte b_exit item 0<, f_code .byte b_zlt .byte b_exit item 0>, f_code .byte b_zgt .byte b_exit item "=", f_code .byte b_eq .byte b_exit item <, f_code .byte b_lt .byte b_exit item >, f_code .byte b_gt .byte b_exit item "<=", f_code .byte b_lteq .byte b_exit item ">=", f_code .byte b_gteq .byte b_exit item type, f_code .byte b_type .byte b_exit item expect, f_code .byte b_expect .byte b_exit item emit, f_code .byte b_emit .byte b_exit item count, f_code .byte b_count .byte b_exit item "(\")", f_code .byte b_str .byte b_exit item "(.\")", f_code .byte b_strp .byte b_exit item var8, f_code .byte b_var8 .byte 0f - . 0: .byte b_exit item var16, f_code .byte b_var16 .word 0f - . 0: .byte b_exit item base base: .byte b_var0 v_base: .quad 10 holdbuf_len = 70 item holdbuf holdbuf: .byte b_var0 .space holdbuf_len item holdpoint holdpoint: .byte b_var0 .quad 0 item span span: .byte b_var0 v_span: .quad 0 # : hold holdpoint @ 1- dup holdbuf > if drop drop else dup holdpoint ! c! then ; item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_get # @ .byte b_wm # 1- .byte b_dup # dup .byte b_call8 .byte holdbuf - . - 1 # holdbuf .byte b_gt # > .byte b_qbranch8 # if .byte 0f - . .byte b_drop # drop .byte b_drop # drop .byte b_branch8 #     ( then) .byte 1f - . 0: .byte b_dup # dup .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_set # ! .byte b_set8 # c! 1: .byte b_exit # ; # : # base /mod swap dup 10 < if c" 0 + else 10 - c" A + then hold ; item # conv: .byte b_call16 .word base - . - 2 # base .byte b_get # @ .byte b_divmod # /mod .byte b_swap # swap .byte b_dup # dup .byte b_lit8 .byte 10 # 10 .byte b_lt # < .byte b_qnbranch8 # if .byte 0f - . .byte b_lit8 .byte '0' # c" 0 .byte b_add # + .byte b_branch8 # else .byte 1f - . 0: .byte b_lit8 .byte '?' # c" A .byte b_add # + 1: .byte b_call16 .word hold - . - 2 # hold .byte b_exit # ; # : <# holdbuf 70 + holdpoint ! ; item <# conv_start: .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_call16 .word holdpoint - . - 2 .byte b_set .byte b_exit # : #s do # dup 0=until ; item #s conv_s: .byte b_call8 .byte conv - . - 1 .byte b_dup .byte b_qbranch8 .byte conv_s - . .byte b_exit # : #> holdpoint @ holdbuf 70 + over - ; item #> conv_end: .byte b_call16 .word holdpoint - . - 2 .byte b_get .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_over .byte b_sub .byte b_exit item . dot: .byte b_dup .byte b_abs .byte b_call8 .byte conv_start - . - 1 .byte b_lit8 .byte ' ' .byte b_call16 .word hold - . - 2 .byte b_call8 .byte conv_s - . - 1 .byte b_drop .byte b_zlt .byte b_qnbranch8 .byte 1f - . .byte b_lit8 .byte '-' .byte b_call16 .word hold - . - 2 1: .byte b_call8 .byte conv_end - . - 1 .byte b_type .byte b_exit item tib tib: .byte b_var0 v_tib: .quad 0 item #tib ntib: .byte b_var0 v_ntib: .quad 0 item >in bin: .byte b_var0 v_in: .quad 0 # : .s depth dup . c": emit do dup while dup pick . 1- again drop ; item .s # 11 22 33 prstack: .byte b_depth # 11 22 33 3 .byte b_dup # 11 22 33 3 3 .byte b_strp .byte 2 .ascii "( " .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_strp # 11 22 33 3 .byte 3 .ascii "): " .byte b_dup, b_zlt .byte b_qnbranch8, 1f - . .byte b_strp .byte 14 .ascii "\nStack fault!\n" .byte b_quit 1: .byte b_dup # 11 22 33 3 3 .byte b_qnbranch8 # 11 22 33 3 .byte 2f - . .byte b_dup # 11 22 33 3 3 .byte b_pick # 11 22 33 3 11 .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_wm # 11 22 33 2 .byte b_branch8 .byte 1b - . 2: .byte b_drop # 11 22 33 .byte b_exit .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # ,    .byte b_qbranch8, 3f - . #     0, ,      3 .byte b_type #    .byte b_strp #   .byte 19 #     .ascii " : word not found!\n" .byte b_quit #    3: .byte b_nip, b_nip #  ,     ( b_over, b_over) 2: #       .byte b_depth #    .byte b_zlt # ,   0 ( 0<) .byte b_qnbranch8, interpret_ok - . #   ,    ,   .byte b_strp #    .byte 14 .ascii "\nstack fault!\n" .byte b_quit #    interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit last_item: .byte b_var0 item bye, f_code .byte b_bye .section .text .global _start #     _start: mov rbp, rsp sub rbp, stack_size lea r8, start mov init_stack, rsp mov init_rstack, rbp jmp _next b_var0 = 0x28 bcmd_var0: push r8 b_exit = 0x17 bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_num1 = 0x03 bcmd_num1: push 1 jmp _next b_num2 = 0x04 bcmd_num2: push 2 jmp _next b_num3 = 0x05 bcmd_num3: push 3 jmp _next b_num4 = 0x06 bcmd_num4: push 4 jmp _next b_num8 = 0x07 bcmd_num8: push 8 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_dup = 0x30 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_wp = 0x18 bcmd_wp: incq [rsp] jmp _next b_add = 0x21 bcmd_add: pop rax add [rsp], rax jmp _next b_sub = 0x22 bcmd_sub: pop rax sub [rsp], rax jmp _next b_mul = 0x23 bcmd_mul: pop rax pop rbx imul rbx push rax jmp _next b_div = 0x24 bcmd_div: pop rbx pop rax cqo idiv rbx push rax jmp _next b_mod = 0x25 bcmd_mod: pop rbx pop rax cqo idiv rbx push rdx jmp _next b_divmod = 0x26 bcmd_divmod: pop rbx pop rax cqo idiv rbx push rdx push rax jmp _next b_abs = 0x27 bcmd_abs: mov rax, [rsp] or rax, rax jge _next neg rax mov [rsp], rax jmp _next b_drop = 0x31 bcmd_drop: add rsp, 8 jmp _next b_swap = 0x32 bcmd_swap: pop rax pop rbx push rax push rbx jmp _next b_rot = 0x33 bcmd_rot: pop rax pop rbx pop rcx push rbx push rax push rcx jmp _next b_mrot = 0x34 bcmd_mrot: pop rcx pop rbx pop rax push rcx push rax push rbx jmp _next b_over = 0x35 bcmd_over: push [rsp + 8] jmp _next b_pick = 0x36 bcmd_pick: pop rcx push [rsp + 8*rcx] jmp _next b_roll = 0x37 bcmd_roll: pop rcx mov rbx, [rsp + 8*rcx] roll1: mov rax, [rsp + 8*rcx - 8] mov [rsp + 8*rcx], rax dec rcx jnz roll1 push rbx jmp _next b_depth = 0x38 bcmd_depth: mov rax, init_stack sub rax, rsp sar rax, 3 push rax jmp _next b_nip = 0x39 bcmd_nip: pop rax mov [rsp], rax jmp _next b_get = 0x40 bcmd_get: pop rcx push [rcx] jmp _next b_set = 0x41 bcmd_set: pop rcx pop rax mov [rcx], rax jmp _next b_get8 = 0x42 bcmd_get8: pop rcx movsx rax, byte ptr [rcx] push rax jmp _next b_set8 = 0x43 bcmd_set8: pop rcx pop rax mov [rcx], al jmp _next b_get16 = 0x44 bcmd_get16: pop rcx movsx rax, word ptr [rcx] push rax jmp _next b_set16 = 0x45 bcmd_set16: pop rcx pop rax mov [rcx], ax jmp _next b_get32 = 0x46 bcmd_get32: pop rcx movsx rax, dword ptr [rcx] push rax jmp _next b_set32 = 0x47 bcmd_set32: pop rcx pop rax mov [rcx], eax jmp _next b_setp = 0x48 bcmd_setp: pop rcx pop rax add [rcx], rax jmp _next b_setm = 0x49 bcmd_setm: pop rcx pop rax sub [rcx], rax jmp _next b_2r = 0x60 bcmd_2r: pop rax sub rbp, 8 mov [rbp], rax jmp _next b_r2 = 0x61 bcmd_r2: push [rbp] add rbp, 8 jmp _next b_rget = 0x62 bcmd_rget: push [rbp] jmp _next # 0= b_zeq = 0x50 bcmd_zeq: pop rax or rax, rax jnz rfalse rtrue: push -1 jmp _next rfalse: push 0 jmp _next # 0< b_zlt = 0x51 bcmd_zlt: pop rax or rax, rax jl rtrue push 0 jmp _next # 0> b_zgt = 0x52 bcmd_zgt: pop rax or rax, rax jg rtrue push 0 jmp _next # = b_eq = 0x53 bcmd_eq: pop rbx pop rax cmp rax, rbx jz rtrue push 0 jmp _next # < b_lt = 0x54 bcmd_lt: pop rbx pop rax cmp rax, rbx jl rtrue push 0 jmp _next # > b_gt = 0x55 bcmd_gt: pop rbx pop rax cmp rax, rbx jg rtrue push 0 jmp _next # <= b_lteq = 0x56 bcmd_lteq: pop rbx pop rax cmp rax, rbx jle rtrue push 0 jmp _next # >= b_gteq = 0x57 bcmd_gteq: pop rbx pop rax cmp rax, rbx jge rtrue push 0 jmp _next b_var8 = 0x29 bcmd_var8: push r8 b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_var16 = 0x30 bcmd_var16: push r8 b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next b_qnbranch8 = 0x14 bcmd_qnbranch8: pop rax or rax, rax jz bcmd_branch8 inc r8 jmp _next b_qnbranch16 = 0x15 bcmd_qnbranch16:pop rax or rax, rax jz bcmd_branch16 add r8, 2 jmp _next b_bad = 0x00 bcmd_bad: mov rax, 1 #    1 - sys_write mov rdi, 1 #   1  stdout mov rsi, offset msg_bad_byte #     mov rdx, msg_bad_byte_len #   syscall #   mov rax, 60 #    1 - sys_exit mov rbx, 1 #    1 syscall #   b_bye = 0x01 bcmd_bye: mov rax, 1 #    1 - sys_write mov rdi, 1 #   1  stdout mov rsi, offset msg_bye #     mov rdx, msg_bye_len #   syscall #   mov rax, 60 #    60 - sys_exit mov rdi, 0 #    0 syscall #   b_strp = 0x83 bcmd_strp: movsx rax, byte ptr [r8] inc r8 push r8 add r8, rax push rax b_type = 0x80 bcmd_type: mov rax, 1 #    1 - sys_write mov rdi, 1 #   1 - stdout pop rdx #   pop rsi #   push r8 syscall #   pop r8 jmp _next b_expect = 0x88 bcmd_expect: mov rax, 0 #    1 - sys_read mov rdi, 0 #   1 - stdout pop rdx #   pop rsi #   push r8 syscall #   pop r8 mov rbx, rax or rax, rax jge 1f xor rbx, rbx 1: mov v_span, rbx jmp _next b_str = 0x82 bcmd_str: movzx rax, byte ptr [r8] lea r8, [r8 + rax + 1] jmp _next b_count = 0x84 bcmd_count: pop rcx movzx rax, byte ptr [rcx] inc rcx push rcx push rax jmp _next b_emit = 0x81 bcmd_emit: pop rax mov rsi, offset emit_buf #   mov [rsi], al mov rax, 1 #    1 - sys_write mov rdi, 1 #   1 - stdout mov rdx, 1 #   push r8 syscall #   pop r8 jmp _next b_blword = 0xF0 bcmd_blword: mov rsi, v_tib #    mov rdx, rsi #   RDX       mov rax, v_in #     mov rcx, v_ntib #    mov rbx, rcx add rsi, rax #  RSI -      sub rcx, rax #     jz 3f word2: lodsb #   AL  RSI   cmp al, ' ' ja 1f #    (    ) dec rcx jnz word2 #    3: sub rsi, rdx mov v_in, rsi push rcx jmp _next 1: lea rdi, [rsi - 1] # RDI = RSI - 1 ( ) dec rcx jz word9 word3: lodsb cmp al, ' ' jbe 2f dec rcx jnz word3 word9: inc rsi 2: mov rax, rsi sub rsi, rdx #        (   ) cmp rsi, rbx jle 4f mov rsi, rbx 4: mov v_in, rsi sub rax, rdi dec rax jz word1 push rdi #   word1: push rax #   jmp _next b_quit = 0xF1 bcmd_quit: lea r8, quit mov rsp, init_stack mov rbp, init_rstack jmp _next b_find = 0xF2 bcmd_find: pop rbx #   pop r9 #   mov rdx, v_context mov rdx, [rdx] #        #   find0: mov al, [rdx] #  and al, 3 #   -     ,     ,    or al, al jz find_l8 cmp al, 1 jz find_l16 cmp al, 2 jz find_l32 mov r10, [rdx + 1] #  64  lea rsi, [rdx + 9] #   jmp find1 find_l32: movsx r10, dword ptr [rdx + 1] #  32  lea rsi, [rdx + 5] #   jmp find1 find_l16: movsx r10, word ptr [rdx + 1] #  16  lea rsi, [rdx + 3] #   jmp find1 find_l8: movsx r10, byte ptr [rdx + 1] #  8  lea rsi, [rdx + 2] #   find1: movzx rax, byte ptr [rsi] #       cmp rax, rbx jz find2 #      find3: or r10, r10 jz find_notfound #  ,    add rdx, r10 #     jmp find0 #  ,   find2: inc rsi mov rdi, r9 mov rcx, rax repz cmpsb jnz find3 #   push rdx jmp _next find_notfound: push r10 jmp _next b_cfa = 0xF3 bcmd_cfa: pop rdx #    mov al, [rdx] #  and al, 3 #   -     ,     ,    or al, al jz cfa_l8 cmp al, 1 jz cfa_l16 cmp al, 2 jz cfa_l32 lea rsi, [rdx + 9] #   (64  ) jmp cfa1 cfa_l32: lea rsi, [rdx + 5] #   (32  ) jmp cfa1 cfa_l16: lea rsi, [rdx + 3] #   (16  ) jmp cfa1 cfa_l8: lea rsi, [rdx + 2] #   (8  ) cfa1: xor rax, rax lodsb add rsi, rax push rsi jmp _next b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 #       pop r8 #  - jmp _next b_numberq = 0xF5 bcmd_numberq: pop rcx #   pop rsi #  xor rax, rax #   xor rbx, rbx #     mov r9, v_base #  xor r10, r10 #   or rcx, rcx jz num_false mov bl, [rsi] cmp bl, '+' jnz 1f inc rsi dec rcx jz num_false jmp num0 1: cmp bl, '-' jnz num0 mov r10, 1 inc rsi dec rcx jz num_false num0: mov bl, [rsi] cmp bl, '0' jb num_false cmp bl, '9' jbe num_09 cmp bl, 'A' jb num_false cmp bl, 'Z' jbe num_AZ cmp bl, 'a' jb num_false cmp bl, 'z' ja num_false sub bl, 'a' - 10 jmp num_check num_AZ: sub bl, 'A' - 10 jmp num_check num_09: sub bl, '0' num_check: cmp rbx, r9 jge num_false mul r9 add rax, rbx inc rsi dec rcx jnz num0 or r10, r10 push rax push 1 jmp _next num_false: xor rcx, rcx push rcx jmp _next 

Der Quellcode wird immer größer, deshalb bringe ich ihn zum letzten Mal hierher.

Jetzt befindet sich sein Wohnort auf dem Github: https://github.com/hal9000cc/forth64
An derselben Stelle finden Sie im Ordner bin die Version, die bereits für Linux x64 kompiliert wurde. Wer Linux hat, kann herunterladen und ausführen.

Und wer hat Windows - Sie können WSL (Windows Subsystem für Linux) installieren. Ich ging in die Ferien und tat genau das. Es stellte sich als sehr einfach heraus, es dauerte ungefähr 5 Minuten. Es gab nur einen Moment, es startete nicht sofort, das Subsystem musste über den PowerShell-Befehl „eingeschaltet“ werden. Folgen Sie dem Link aus der Fehlermeldung, führen Sie den Befehl aus und es hat funktioniert.

Es gibt aber auch eine Möglichkeit für echte Inder, alles unter Windows auszuführen :) Es ist nicht schwierig, dies zu tun. Wiederholen Sie einfach ein paar Wörter, die mit dem System interagieren.

Das ist alles! Nächstes Mal führen wir den Compiler aus.

Es wird die Möglichkeit geben, neue Wörter zusammenzustellen, es wird Bedingungen und Zyklen geben. Tatsächlich wird es möglich sein, auf eine mehr oder weniger standardmäßige Festung zu schreiben, sie in Bytecode zu kompilieren und auszuführen. Nun, es wird möglich sein, ernstere Tests durchzuführen und die Leistung der Byte-Maschine zu überprüfen.

Fortsetzung: Byte-Maschine für das Fort (und nicht nur) in Native American (Teil 4)

Source: https://habr.com/ru/post/de435236/


All Articles