2019-12-08, 17:32 PM MEZ

Nein, wissen Sie, was, ich habe etwas viel besseres im Auge. Ich schreibe einen Pascal-Compiler, aber diesmal einen, der seiner wördig ist. Und den bringe ich so an, dass er mit PHP auf meiner Homepage verwenbar ist.

Dazu muss ich zunächst einen Lexer schreiben.

Ich schreibe den Compiler so, dass er einen Syntaxbaum erstellt. Dieser Syntaxbaum wird in Form einer HTML-Liste geposted. Allerdings: Es wird kein anderer Code erzeugt. Das heißt, es tut der Lexer seine Arbeit und der Parser, es wird aber kein Code generiert.

2019-12-08, 17:57 PM MEZ

Ich mache den Lexer anders als gewohnt. Ich gehe den Pascal-Quelltext durch. Dabei wird eine lineare Liste erzeugt. Es wird alles, als ein einzelnes Zeichen gewertet, was zum Beispiel eine Zeichenkette ist, auf die ein ' ' oder ein '\n' folgt. Oder, auf die ein '(' folgt.

Nein, das mache ich nicht. Ich schreibe einen Lexer, der eine Zeichenkette liefert.

Dabei werden Zeichenketten zusammengefasst und Sonderzeichen, einzeln gesehen. Und: Es werden sogar ' ' und '\n' von dem Lexer wiedergeben, als Sonderzeichen und zwar als das, was sie sind. Der Parser hat dann nachher die Müglichkeit diese zu ignorieren. Ansonsten gibt es noch Zeichenketten mit Ziffern. Der einfachkeitshalber werden Ziffern und 'Buchstaben' niemals als eine Zeichenkette aufgefasst.

2019-12-08, 18:16 PM MEZ

So, ich habe es geschafft, mit dem Lexer, nächster Beitrag.

2019-12-08, 18:16 PM MEZ

#include <stdio.h>

/************************* Lexer ************************/

void lex_get_token_from_stream(FILE *fp, char *str) {
    int i;
    int ch;

    ch = fgetc(fp);
    i = 0;
    
    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((ch >= '0') && (ch <= '9') && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if(feof(fp)) {
        str[i] = ch;
        str[i+1] = 0;
    }
    else {
        str[i] = ch;
        str[i+1] = 0;
    }
}

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    while (!feof(fp)) {
        lex_get_token_from_stream(fp, str);
        printf("%s\n", str);
    }
    fclose(fp);
    
}

2019-12-08, 18:16 PM MEZ

Aber, jetzt das Lustige: Ich filtere die White-Space trotzdem raus. Aber in einer extra-Funktion. Bisher habe ich eine Lexer-Funktion geschrieben, die alle Token einzeln ausgibt, aber dazu gehüren auch die White-Space. Aber, das war gut. Weil: Jetzt kann ich eine zweite Funktion, um diese Funktion herum schreiben, die dann White-Space herausfiltert.

2019-12-08, 18:21 PM MEZ 

#include <stdio.h>

/************************* Lexer ************************/

void lex_get_token_from_stream(FILE *fp, char *str) {
    int i;
    int ch;

    ch = fgetc(fp);
    i = 0;
    
    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((ch >= '0') && (ch <= '9') && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if(feof(fp)) {
        str[i] = ch;
        str[i+1] = 0;
    }
    else {
        str[i] = ch;
        str[i+1] = 0;
    }
}

void lex_get_token_from_stream_ws(FILE *fp, char *str) {
    lex_get_token_from_stream(fp, str);
    while (((str[0] == '\n') || (str[0] == '\t') || (str[0] == '\r') || (str[0] == ' ')) && !feof(fp))
        lex_get_token_from_stream(fp, str);
}

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    while (!feof(fp)) {
        lex_get_token_from_stream_ws(fp, str);
        printf("%s\n", str);
    }
    fclose(fp);
    
}

2019-12-08, 18:21 PM MEZ

Was jetzt doch noch wichtig ist, ist das Unterstriche '_' als Buchstaben gewertet werden.

2019-12-08, 18:22 PM MEZ

#include <stdio.h>

/************************* Lexer ************************/

void lex_get_token_from_stream(FILE *fp, char *str) {
    int i;
    int ch;

    ch = fgetc(fp);
    i = 0;
    
    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((ch >= '0') && (ch <= '9') && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if(feof(fp)) {
        str[i] = ch;
        str[i+1] = 0;
    }
    else {
        str[i] = ch;
        str[i+1] = 0;
    }
}

void lex_get_token_from_stream_ws(FILE *fp, char *str) {
    lex_get_token_from_stream(fp, str);
    while (((str[0] == '\n') || (str[0] == '\t') || (str[0] == '\r') || (str[0] == ' ')) && !feof(fp))
        lex_get_token_from_stream(fp, str);
}

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    while (!feof(fp)) {
        lex_get_token_from_stream_ws(fp, str);
        printf("%s\n", str);
    }
    fclose(fp);
    
}

2019-12-08, 18:22 PM MEZ

Jetzt machen wir aber den reinen Code. Wir machen keine Kopfinformationen. Wie, Variablenamen und Konstanten. Sondern der Code.

Die Funktionen sehen dann etwa wie folgt aus:

Normalerweise, bei arithmetischen Ausdröcken haben wir:

expr(), term(), factor()

Und es wird in etwa aufgerufen, wie

expr()
 if(...)
   expr();
 term();
}

Jetzt haben wir in Pascal Ausdröcke, wie WHILE, IF, IF ELSE, ...
In diesem Fall, nennen wir die Funktionen nicht EXPR(), TERM(), FACTOR(), sondern einfach WHILE(), IF()...

Diese rufen entsprechend geschachtelt, die anderen auf. Das war es schon.

Aber, meine Damen und Herren, Sie glauben es nicht! Wir brauchen immer noch, ein EXPR(), TERM(), FACTOR(). Aber woför eigentlich? 
Ganz einfach: Wir haben immer noch arithmetische Ausdröcke.

Und wo stehen die? Ganz einfach, zum Beispiel in unserem WHILE.

Da steht ein EXPR() in unserem Ausdruck bei WHILE.

Kein WHILE (...) DO ohne för "..." ein EXPR() ein zu setzen.

Jetzt ist die Frage, wie wird unser Syntaxbaum eigentlich bei WHILE aufgebaut.

Nun, wir haben einen Rechten und einen Linken Knoten.
Und, wenn wir WHILE haben, dann: Ist der linke Knoten der EXPR() in der Bedingung von WHILE und der Rechte, die Folge von Anweisungen, die dann folgt.

Aber, was künnte, denn diese Folge von Anweisungen sein?
Nun ein Block. Und ein Block beginnt mit BEGIN und endet mit END. Und der steht auf der Rechten Seite von WHILE in den Knoten. Der einfachkeitshalber machen wir es uns eventuell gemötlich. Indem, wir immer BEGIN und END fordern. Aber, das ist nicht nütig. Sollte kein BEGIN und kein END folgen, auf WHILE, ist der Rechte Knoten einfach der komplette Ausdruck nach WHILE. Was ist jetzt allerdings dieses BEGIN und dieses END? Das ist etwas lustiges: Weil: Das sind sind '(' und ')' wie in den arithemtischen Ausdröcken, sind aber stattdessen BEGIN und END nicht in Ausdröcken, sondern in unserem Programm.
Wo mössen wir das im Syntaxbaum einbauen? Eventuell gar nicht. Hängt davon ab, was för einen Syntaxbaum wir benutzen. Einen, der die Klammern speicher, oder nicht. Wir machen es ohne. Der Syntaxbaum stellt die richtige Ordnung her. BEGIN und END sind Klammern, die Ordnung existiert auch so.

Jetzt mössen wir lediglich solche Funktionen schreiben:

IF ()  {
  Expression()
  if (...)
     IF()
else  if(...)
   WHILE
else  if(...)
    FOR
}

WHILE () {
  Expression()
  if(...)
   IF()...
}

Was wir sonst an Expr(), Term() und Factor() för arithmetische Ausdröcke haben, haben wir hier an IF, WHILE, ... im Code.
Nur: Was ist dieses BEGIN und END? Daför gibt es beim Parsern för arithmetische Ausdröcke eine einfache Antwort: Hier stoßen wir auf '(' in den Funktionen, und: Diese werden mit if jeweils in der Funktion abgefragt.

Jetzt mössen wir uns nur noch eines fragen: Man künnte es deutlich so formulieren: Was ist der Unterschied zwischen
1. Expression
2. Statement.

Das ist eine Lustige Sache. Weil Statement ist nichts anders, als ein Expression mit einen var := Expression. Dabei! Das Statement steht nicht in WHILE(...) . Da steht ein Expression. Zum Statement wird ein Expression öber die Zuweisung mit := . 
Aber: Wenn wir unsere BEGIN und END anschauen, die zum Beispiel auf WHILE folgen, stehen hier lauter Statements und nicht Expressions.

Letzten endes ist das so: 

Ein WHILE ist ein WHILE gefolgt von einem EXPR und einem Compound-Statement. Ein Compound Statement, ist das, was wir unter einem Block verstehen, etwas, das mit BEGIN anfängt und mit END aufhürt. Aber: Wir künnen alles als Compound Statement zählen, was an der Stelle von Compund Statement bei WHILE steht, auch, wenn es nur ein Simple Statement zum Beispiel ist, eine Anweisung ohne BEGIN und END.

Also, muss die Funktion, WHILE so aussehen:

WHILE() {
   expression();
   compound_statement();
}

Oder sogar noch besser:

WHILE () {
   expression();
   if(... = BEGIN) {
     compound_statement();
     if(... = END)
       ...
    else
      syntax_error();
   else
     simple_statement();
}

2019-12-08, 19:07 PM MEZ

Jetzt mössen wir aber unseren Lexer anpassen. Das sieht dann wie folgt aus, nächster Beitrag. Denn wir dörfen den Stream eventuell nur erhühen, wenn das richtige Zeichen kam.

2019-12-08, 19:07 PM MEZ

#include <stdio.h>

/************************* Lexer ************************/

int lex_get_token_from_stream(FILE *fp, char *str) {
    int i;
    int ch;

    ch = fgetc(fp);
    i = 0;
    
    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp)) 
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && !feof(fp)) {
        str[i] = ch;
        i++;
        ch = fgetc(fp);
        while ((ch >= '0') && (ch <= '9') && !feof(fp)) {
            str[i] = ch;
            i++;
            ch = fgetc(fp);
        }
        if(!feof(fp))
            ungetc(ch, fp);
        str[i] = 0;
    }
    else if(feof(fp)) {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    else {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    return i;
}

int lex_get_token_from_stream_ws(FILE *fp, char *str) {
    int i;
    
    i = lex_get_token_from_stream(fp, str);
    while (((str[0] == '\n') || (str[0] == '\t') || (str[0] == '\r') || (str[0] == ' ')) && !feof(fp))
        i = lex_get_token_from_stream(fp, str);
return i;
}

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    int i;
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    while (!feof(fp)) {
        i = lex_get_token_from_stream_ws(fp, str);
        printf("%s        %i\n", str, i);
    }
    fclose(fp);
    
}

2019-12-08, 19:08 Uhr PM MEZ

Jetzt machen wir die Sache noch ein Mal anders: Wir lesen den Stream in einen Buffer ein. Wir föhren ein allgemein index (i - zum Beispiel) und der wird nur erhüht, wenn es sein darf. Der Lexer ließt jetzt aus dem Buffer und nicht aus dem Stream.

2019-12-08, 19:19 Uhr PM MEZ

So, das sieht jetzt wie folgt aus, nächster Beitrag.

2019-12-08, 19:20 Uhr PM MEZ

#include <stdio.h>

/************************* Lexer ************************/

char buf[1024*1024];
int  buf_i;
int  buf_len;

void get_buf(FILE *fp) {
    int i;
    while(!feof(fp)) {
        buf[i] = fgetc(fp);
        i++;
    }
    buf_len = i;
return;
}

int lex_get_token_from_stream(char *str) {
    int i;
    int ch;

    ch = buf[buf_i];
    i = 0;
    
    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && ((buf_i + i ) < buf_len)) {
        str[i] = ch;
        i++;
        ch = buf[buf_i + i];
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && ((buf_i + i) < buf_len)) {
            str[i] = ch;
            i++;
            ch = buf[buf_i + i];
        }
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && ((buf_i + i ) < buf_len)) {
        str[i] = ch;
        i++;
        ch = buf[buf_i + i];
        while ((ch >= '0') && (ch <= '9') && ((buf_i + i ) < buf_len)) {
            str[i] = ch;
            i++;
            ch = buf[buf_i + i];
        }
        str[i] = 0;
    }
    else if((buf_i + i) >= buf_len) {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    else {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    return i;
}

int lex_get_token_from_stream_ws(char *str) {
    int i;
    
    i = lex_get_token_from_stream(str);
    while (((str[0] == '\n') || (str[0] == '\t') || (str[0] == '\r') || (str[0] == ' ')) && ((buf_i + i) < buf_len)) {
        buf_i += i;
        i = lex_get_token_from_stream(str);
    }
return i;
}

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    int i;
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    get_buf(fp);
    while((buf_i + i) < buf_len) {
        i = lex_get_token_from_stream_ws(str);
        printf("%s        %i\n", str, i);
        buf_i += i;
    }
    fclose(fp);
    
}

2019-12-08, 19:21 Uhr PM MEZ

Jetzt stellen wir den Parser her. Diesen zunächst mal mit dem jetztzigen Lexer und dazu aber nur arithmetische Ausdröcke. Dann sehen wir, ob es so weit funkitoniert.

2019-12-08, 19:29 Uhr PM MEZ

Das liefert öbrigens richtigerweise mein Lexer.

2019-12-08, 19:29 Uhr PM MEZ

Wir dörfen jetzt eines nicht verwechseln: Den Syntaxbaum mit einem Parser. Der Parser hat nichts mit Syntaxbäumen zu tun. Syntaxbäume sind ein Zwischencode.
Und der Parser ist zunächst eine Maschine, die öberpröft, ob die Syntax entsprechend stimmt.
Obwohl - der Schritt vom Parser zum Syntaxbaum schnell gemacht ist! Sie mössen jetzt nicht erschrocken sein, oh, du hast einen Parser erstellt, aber keinen Syntaxbaum! Das stimmt nicht - die eigentliche Herausforderung ist der Parser. Der Syntaxbaum rankt sich nachher im Parser! Der Parser ist der Hauptteil, nicht der Syntaxbaum, auch, wenn der Syntaxbaum als etwas aussieht, weil er ein Code ist.
Generell ist es ganz einfach, mit einem Parser einen Syntaxbaum zu machen. Dazu muss der Parser aber erst ein Mal da sein. Der Syntaxbaum wird dann quasi in den Parser eingebaut. 
Aber! Den Syntaxbaum, sage ich mal generell so, gibt es nicht. Mag sein, dass es den gibt. Aber: Der lässt Abweichungen zu. D.h. was wir erzeugen ist so oder so unser Problem. Man künnte sagen, der eine baut einen Interpreter, der in dem HTML-Gebilde arbeitet, der nächst einen, der eine ausföhrbare Datei erzeugt. Dementsprechend künnen wir auch unseren Syntaxbaum nach belieben gestalten. Und selbst, wenn wir nicht das haben, was andere haben. Das ist unser Problem. 
Nicht so bei der Sprache PASCAL. Es gibt nur eine Sprache PASCAL. Und nicht viele. Auch, wenn sie abspecken kann.

Aber, Sie sehen es selber. Wenn ich einen typischen Syntaxbaum nehme, wo steht da was? Ein Syntaxbaum ist typischerweise ein binärer Baum.

Wenn wir ein WHILE nehmen sieht das ganz klar aus: Im linken Knoten der Expression, im rechten Knoten, das Compound-Statement.
Doch, wie sieht es mit Compound-Statements aus? Wenn wir alle Anweisungen im Compound-Statement, die untereinander stehen, im jeweils rechten Knoten unterbringen, dann funktioniert das zunächst. Bis wir feststellen, wir sind in einem Compound-Statement, das WHILE gehürt. Danach folgen wieder eine Menge an Anweisungen, die nicht zu WHILE gehüren. Wenn die jetzt rechts darunter stehen, wörden diese dazu gehüren.

Der Trick besteht jetzt darin. Wir sind generell in einem Compound-Statement, und alle Anweisungen, die untereinander im Code stehen, stehen im rechten Knoten. Seit denn es folgt ein IF oder ein WHILE. Das landet im Linken-Knoten und die Anweisungen, die darauf folgen, wieder im Rechten.

Aber: Das ist der Witz: Wir haben eine Funktion im Parser WHILE. Sollte diese bedient werden, dann wird der linke Knoten bedient. Das Gegenstöck dazu wäre ein Simple-Statement. Das Simple-Statement kommt auf die Rechte seite.

Und: Wir sind in einem sagen wir Compund-Statement. Vom Syntaxbaum sieht das so aus:

Compund-Statement
  If(WHILE)
    linkerKnoten
   if(Compund-Statement)
    lnkerKnoten
  if(Simple-Statement)
    rechterKnoten
    
2019-12-08, 19:43 Uhr PM MEZ

So, ich mache jetzt trotzdem Schluss för heute, mir geht einiges auf den Sack.
Ich gehe jetzt in die Stadt mit Regenschirm - und Tabak kaufen.

2019-12-11

Ich programmiere nachher den Compiler för Pascal weiter. Jetzt mache ich aber erst einen Spaziergang.
Halt! Ich werde heute zwei Sachen tun: Ich habe neulich davon gesprochen, dass ich auch anderer Leute Software vorstellen. C vorstellen heißt C vorstellen, das tut nur mit der Software anderer. Also auch Linux. Also habe ich gesagt: Ich mache einen Artikel zu Linux.
Dieser wird es aber in sich haben: In der virtuellen Maschine VirtualBox lassen sich Filme aufnehmen. Ich werde den Installationsprozess aufnehmen.
Und: Ich stelle meine Verbindung zu Unitymedia vor. Mainboards und Sources, wo man Gehäuse her bekommt.

2019-12-11

Wir fangen gleich mal mit dem Compiler an.

Und zwar: Die Funktionen 
expression(), term(), factor() werden wir gleich realisieren. Denn die brauchen wir. Und wir föhren / und - gar nicht ein. Sondern nur + und *. Es geht uns um die Struktur von Pascal-Programmen. Diese Funktionen künnen wir aus Algorithmen in C gleich öbernehmen.

Aber: Wir werden zunächst die FOR-Schleife realisieren. 
Was ist öberhaupt eine FOR-Schleife? Nun etwas, wie

for i := 1 to 10 do

und zwar: 
FOR expression TO expression DO

Wobei, das erste expression nicht ganz richtig ist. Das erste Expression ist eigentlich ein Assignment Statement. Heißt: Zuweisung. Wir mössen zwischen Ausdröcken und Zuweisungen unterscheiden.

Eine Zuweisung (Assignment Statement) ist

var := expression

Wie realisieren wir das jetzt? Wir realisieren Schritt för Schritt jede Funktion. Wenn FOR ein Element in Pascal ist, schreiben wir in unserem Compiler in C eine entsprechende Funktion. Eine Funktion for(). 

Wir schreiben eine Funktion for()

Und ein for() ist ein FOR gefolgt von einem Assignment Statement, gefolgt von einem TO gefolgt von einem expression, gefolgt von einem DO.

Merken Sie etwas: Das DO ist gefolgt von einem Statement. Egal was för ein Statement:

- Simple Statement
- Assignment Statement
- Empty Statement
- Compound Statement
- ...

Also ist ein FOR eigentlich ein FOR gefolgt von einem Assignment Statement gefolgt von einem TO gefolgt von einem Expression gefolgt von einem DO gefolgt von einem Statement. Egal was för ein Statement.

Dann mössen wir die Funktion Statement() einföhren, das machen wir aber erst nachher, und diese ist entweder ein Simple Statement, Assignment Statement, ... - dies wird durch eine if-Abfrage in Erfahrunge gebracht, ob es sich - innerhalb der Funktion Statement() - um ein Simple Statement, Assignment Statement oder sonst etwas handelt.

Dabei mössen wir jetzt darauf achten, auch, wenn wir das Statement() noch nicht realisieren.

Ein FOR ist ein Assignment Statement gefolgt von einem TO gefolgt von eimen Expression gefolgt von einem DO. Das muss ein Assigment Statement sein und kann kein Compound Statement sein. Also mössen wir auch die Funktion Assignment statement realiseren.

Doch die Frage ist, was ist ein Assignment statement.
Ganz einfach:

Ein Variablenname gefolgt von einem := gefolgt von einem expression.

Die Frage ist, wie testen wir das? Machen wir in unsere Funktion, Assignment Statement() eine if-Abfrage, die öberpröft steht dort := ? Nein, das machen wir nicht. Nein, das machen wir nicht. Wir schreiben eine Funktion SET.

Wenn wir a := 5*x; setzen. Dann heißt das einfach SET. Wir setzen a := 5*x; Deswegen schreiben wir eine Funktion SET. Und diese öberpröft, ob da := steht.

Das machen wir ebenso mit Variablennamen und Expression. För Expression haben wir ja schon eine Funktion.

Die Funktion Assignment-Statement() wird deswegen besonders einfach, sie sieht wie folgt aus:

assignmentstatement() {
   if(varname()) {
     if(set()) {
       if(expression())}}}

Dabei wird auch varname() als eine Funktion realisiert. Diese lässt sich dann modular auch verfeinern. Ohne, dass wir den Code an den anderen Funktionen im Compiler verändern. Zum Beispiel künnte diese Funktion zu Frieden sein, kommt eine Zeichenkette. Sie lässt sich aber verfeinern, ohne dass der gesamte Code verändert werden muss, indem sie Pascal-Schlösselworte wie BEGIN  und END nicht als Variblennamen zulässt.

Jetzt gibt einen weiteren solchen Ausdruck, in der FOR-Schleife. 

Eine FOR-Schleife ist ein FOR gefolgt von einem Assignment-Statement, gefolgt von einem TO gefolgt von einem Expression gefolgt von einem DO.

Jetzt gibt es wie bei "SET" zwei weitere solcher Ausdröcke in der FOR-Schleife, nämlich TO und DO. Diese künnten wir mit strcmp() auch direkt in FOR öberpröfen, das machen wir aber nicht, wir föhren die Funktion to() und die Funktion do() ein. Denn wir bleiben unserem Motto treu.

Diese Funktionen vergleichen intern, die nächste Zeichenkette mit "TO" und "DO" und geben einen Booleschen Wert zuröck.

Jetzt bleibt uns natörlich noch das "FOR". Vergleichen wir das jetzt direkt in unserer Funktion for(), weil wir schon eine solche Funktion haben? Nein, wir föhren eine Funktion for1() ein. Das weitere im nächsten Beitrag. Da erst ein Mal die Funktionen expression(), term(), factor().

2019-12-11

/* Expression */

expression() {
 term();
 if(p[j] == '+') {j++; expression()}}

/* term */

term() {
 factor();
 if((p[j] == '(' || letter(p[j])) term();}

/* factor */

factor() {
 if(p[j] == '(') {j++; expression();
   if(p[j] == ')') j++; else error();
 else if (letter(p[j])) j++; else error();
 if (p[j] == '*') j++;
}

2019-12-11

Jetzt haben diese Funktionen, wie im letzten Beitrag för unseren Code natörlich einen Nachteil. Denn sie entsprechen nicht unserem Lexer. Was hier geschieht, ist das direkte Auslesen, des Zeichen '+' und '(' und so weiter, oder eines Buchstabens (letter()). Das muss aber unser Lexer öbernehmer. Unser Parser schaut nicht nach den Zeichen. Tut er wohl, aber das machen die Funktionen, wie to() in der Bearbeitung der for-Schleife. Bei unserem Lexer ist es allerdings so: Es gibt nicht nur Zeichenfolgen, bestehend aus einem Zeichen, sondern es gibt Zeichenfolgen, die sind 10 Zeichen lang. Das kann unser Lexer auch. Wichtig för unseren Lexer ist, er liefert als Röckgabewert die Zahl der Zeichen des nächsten Tokens zuröck. Und er liefert das nächste Token. Den Index im Tokenstream erhüht unser Lexer nicht. Das tut der Parser nur dann, wenn das richtige geliefert wurde. Und: Wenn der das Parser das tut. Wir sind davon ausgegangen, dass in  unserem Parser alles benannte Funktionen tun, also künnen wir auch eine Funktion einföhren: UpdateTokenIndex().

Jetzt zu unserem Lexer, nächster Beitrag.

2019-12-11

#include <stdio.h>

/************************* Lexer ************************/

char buf[1024*1024];
int  buf_i;
int  buf_len;

void get_buf(FILE *fp) {
    int i;
    while(!feof(fp)) {
        buf[i] = fgetc(fp);
        i++;
    }
    buf_len = i;
return;
}

int lex_get_token_from_stream(char *str) {
    int i;
    int ch;

    ch = buf[buf_i];
    i = 0;
    
    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && ((buf_i + i ) < buf_len)) {
        str[i] = ch;
        i++;
        ch = buf[buf_i + i];
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && ((buf_i + i) < buf_len)) {
            str[i] = ch;
            i++;
            ch = buf[buf_i + i];
        }
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && ((buf_i + i ) < buf_len)) {
        str[i] = ch;
        i++;
        ch = buf[buf_i + i];
        while ((ch >= '0') && (ch <= '9') && ((buf_i + i ) < buf_len)) {
            str[i] = ch;
            i++;
            ch = buf[buf_i + i];
        }
        str[i] = 0;
    }
    else if((buf_i + i) >= buf_len) {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    else {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    return i;
}

int lex_get_token_from_stream_ws(char *str) {
    int i;
    
    i = lex_get_token_from_stream(str);
    while (((str[0] == '\n') || (str[0] == '\t') || (str[0] == '\r') || (str[0] == ' ')) && ((buf_i + i) < buf_len)) {
        buf_i += i;
        i = lex_get_token_from_stream(str);
    }
return i;
}

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    int i;
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    get_buf(fp);
    while((buf_i + i) < buf_len) {
        i = lex_get_token_from_stream_ws(str);
        printf("%s        %i\n", str, i);
        buf_i += i;
    }
    fclose(fp);
    
}

2019-12-11

Das verbinden wir jetzt schon mal mit unserem Parser för expressions. Wir lassen nicht nur einzelne Letters zu, sondern ganze Zeichenketten.

Aber wir föhren die Funktionen 

is_varname()
is_number()

ein. is_number künnen wir nachher beliebig kompliziert machen, zum Beispiel indem es float oder integer zulässt. Das geschieht öber if-Abfragen, wobei in der Funktion

is_number() die Funktionen is_real() oder is_integer() enthalten sind. is_number() liefert entsprechend einen wahren Wert, wenn einer dieser Funktionen einen wahren Wert liefert.

Umgekehrt, beginnen wir jetzt nicht eine Funktion zu realisieren, die is_number() heißt und in der ein Vergleich auf integer stattfindet.

Sondern wir schreiben eine Funktion is_number(), die die Funktion is_integer() aufruft und eben noch nicht is_float() - das künnen wir später machen - und wir realiseren die Funktion is_integer().

Damit beginnen wir jetzt.

2019-12-11

Ich habe den Code jetzt, ich glaube er ist richtig - nächster Beitrag.

2019-12-11

#include <stdio.h>
#include <string.h>

/************************* Lexer ************************/

char buf[1024*1024];
char tmp_str[256];
int  buf_i;
int  buf_len;

void get_buf(FILE *fp) {
    int i;
    i = 0;
    while(!feof(fp)) {
        buf[i] = fgetc(fp);
        i++;
    }
    buf_len = i - 1;
return;
}

int lex_get_token_from_stream(char *str) {
    int i;
    int ch;
    
    if(buf_i == buf_len)
        return 0;
    
    ch = buf[buf_i];
    i = 0;

    if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && ((buf_i + i ) < buf_len)) {
        str[i] = ch;
        i++;
        ch = buf[buf_i + i];
        while ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || (ch == '_')) && ((buf_i + i) < buf_len)) {
            str[i] = ch;
            i++;
            ch = buf[buf_i + i];
        }
        str[i] = 0;
    }
    else if ((ch >= '0') && (ch <= '9') && ((buf_i + i ) < buf_len)) {
        str[i] = ch;
        i++;
        ch = buf[buf_i + i];
        while ((ch >= '0') && (ch <= '9') && ((buf_i + i ) < buf_len)) {
            str[i] = ch;
            i++;
            ch = buf[buf_i + i];
        }
        str[i] = 0;
    }
    else {
        str[i] = ch;
        i++;
        str[i] = 0;
    }
    return i;
}

int lex_get_token_from_stream_ws(char *str) {
    int i;
    
    i = lex_get_token_from_stream(str);
    while (((str[0] == '\n') || (str[0] == '\t') || (str[0] == '\r') || (str[0] == ' ')) && ((buf_i + i) < buf_len)) {
        buf_i += i;
        i = lex_get_token_from_stream(str);
        if(i == 0)
            return 0;
    }
return i;
}

/* parser */

int is_integer(char *str);
void expression(void);
void factor(void);
void term(void);

void error(void) {
    printf("Bad expression\n");
}

int is_opening_round_bracket(char *str) {return ((str[0] == '(') && (str[1] == 0));}
int is_close_round_bracket(char *str) {return ((str[0] == ')') && (str[1] == 0));}
int is_plus_operator(char *str) {return ((str[0] == '+') && (str[1] == 0));}
int is_multiplication_operator(char *str) {return ((str[0] == '*') && (str[1] == 0));}

int is_var_name(char *str) {
    int flag = 1;
    int i;
    
    for(i = 0;  str[i] != 0;  i++)
        if((str[i] <= 'a') || (str[i] >= 'z')) 
            if((str[i] <= 'A') || (str[i] >= 'Z'))
                flag = 0;
return flag;
}

int is_number(char *str) {return is_integer(str);}

int is_integer(char *str) {
    int flag = 1;
    int i;
    
    for(i = 0;  str[i] != 0;  i++)
        if((str[i] <= '0') || (str[i] >= '9'))
            flag = 0;
return flag;
}

void expression(void) {
    int buf_j;
    
    term();
    buf_j = lex_get_token_from_stream_ws(tmp_str);
    if(buf_j == 0)
        return;
    if(is_plus_operator(tmp_str)) {
        buf_i += buf_j;
        expression();
    }
}

void term(void) {
    int buf_j;
    
    factor();
    buf_j = lex_get_token_from_stream_ws(tmp_str);
    if(buf_j == 0)
        return;
    if(is_opening_round_bracket(tmp_str) || is_var_name(tmp_str) || is_number(tmp_str))
        term();
}

void factor(void) {
    int buf_j;
    
    buf_j = lex_get_token_from_stream_ws(tmp_str);
    if(buf_j == 0)
        return;
    if(is_opening_round_bracket(tmp_str)) {
        buf_i += buf_j;
        expression();
        buf_j = lex_get_token_from_stream_ws(tmp_str);
        if(buf_j == 0)
            return;
        if(is_close_round_bracket(tmp_str))
            buf_i += buf_j;
        else {
            error();
        }
    }
    else if (is_var_name(tmp_str) || is_number(tmp_str))
        buf_i += buf_j;
    else {
        error();
    }
    buf_j = lex_get_token_from_stream_ws(tmp_str);
    if(buf_j == 0)
        return;
    if(is_multiplication_operator(tmp_str))
        buf_i += buf_j;
}
        
    
    
    

int main(int argc, char *argv[]) {
    FILE *fp;
    char str[256];
    int i;
    
    if((fp = fopen(argv[1], "r")) == NULL) {
        perror("Can't open file!");
        return -1;
    }
    get_buf(fp);
    while((buf_i + i) < buf_len) {
        i = lex_get_token_from_stream_ws(str);
        printf("%s        %i\n", str, i);
        buf_i += i;
    }
    buf_i = 0;
    expression();
    fclose(fp);
    
}

2019-12-11

Eingegeben werden arithmetische Ausdröcke, wie

(8+1)
((8+1)+2)
((8+1)*2)

usw. Bei diesem Programm, was ich geschrieben habe, werden diese Ausdröcke in einer Datei abgelegt und der Dateinamen als Parameter öbergeben.

Wie im Screenshot erkennbar.

Wenn, ein Fehler in dem arithmetischen Ausdruck ist, etwa bei

((8+1)+())

Dann zeigt der Parser eine Fehlermeldung an "expression error". Es wird noch kein Baum erzeugt. Es wird nur öberpröft, ist die Eingabe richtig.

Gestet habe ich das bisher an:

((8+2)+(1))
((8+2)+())
((8+2)+(12))
((8+2)+(12)
((8+2a))))+(12)
((8+2))))+(12)
((8+2a)+(12))))))

Bei zu viel schließenden Klammern tut es noch nicht. Zu viele schließende Klammern bedeutet nach dieser Syntax, der Ausdruck ist beendet. Jetzt gucken wir mal:

8+2)*3

Ja, das ist noch ein Fehler drin, denn das hier geht. Und das geht natörlich öberhaupt nicht.





Forsetzung
Forsetzung der Forsetzung
Weitere Forsetzung der Forsetzung
Und weiter - fertig für das erste (Erster Meilenstein)
Jetzt gleich zweiter Meilenstein. Mit Code-Erzeugung - Zwischencode!
Kurze Unterbrechung - Schach und Rekursion - Ein Schildkrötenrennen
Der erste Schritt für arithmetische Ausdrücke nach Assembler!
Jetzt gescheite arithmetische Ausdrücke!!!
Jetzt gescheite CMP und Label
Mit verbessertem Lexer
Jetzt mit Parsing von Function und Procedure, sowie ersten Variablen
Jetzt mit ersten Namespaces und Variablennamenverwaltung
Types - jetzt mit Enum und auch Standard-Simpletype, ..., auß: Namespaces so, dass ein Baum da ist
Jetzt bessere Ausgabe für den Baum und Array-Typen