Eigener Parser: Tokens mit mehreren Zeichen einlesen?



  • Wenn alle Tokens durch leerzeichen getrennt sind, dann reicht strtok.
    Aber dann wird aus a+b der Token "a+b", wenn du aber "a" "+" "b" haben willst.

    Dann musst du prüfen ob das aktuelle zeichen noch ein gültiges Zeichen für diesen Token ist, und du darfst den Ursprungsstring nicht verändern wie strtok also läuft es auf malloc free oder statischen speicher herraus.
    Wenn du das ganze auf einem stream machst musst auch dran denken das letzte Zeichen zurückzuschieben oder benutzt eine lookahead variable.
    Kurz ein simpler rekursiver LL(1) Scanner.



  • So ich hab mal ein kleines Beispiel geschustert

    #include <stdlib.h>
    #include <stdio.h>
    #include <ctype.h>
    #include <stdbool.h>
    bool is_wordchar(char c) {
    	return isalpha(c) || c == '_';
    }
    
    int main(void) {
    	char input[1024];
    	putchar('>');
    	fgets(input, sizeof(input), stdin);
    	for(size_t i=0; input[i] != '\0'; i++) {
    		switch(input[i]) {
    			case '\n':	puts("newline"); break;
    			case ' ':	puts("space"); break;
    			case '+':	puts("+"); break;
    			default:	//Wort
    						if(!is_wordchar(input[i])) {
    							printf("Scanner Fehler: unbekanntes Zeichen %c\n", input[i]);
    							exit(EXIT_FAILURE);
    						}
    						for(;is_wordchar(input[i]); i++) {
    							putchar(input[i]);
    						}
    						i--; //i zurücksetzen Stichwort: lookahead
    						putchar('\n');
    		}
    	}
    	return EXIT_SUCCESS;
    }
    


  • @ gary1195:

    Das Problem liegt hier:

    char input[1024];
    

    Es ist nicht bekannt, wie groß der Input ist, demnach kommt ein statisches Array nicht infrage.

    Es scheint so, als müsste ich mich doch mit malloc rumärgern, genau das wollte ich ja umgehen.

    Und es geht mir darum, mehrzeichige Tokens zu speichern, und nicht auszugeben.



  • Ja wenn du wenn du die größe erst zur Laufzeit weißt braucht du malloc oder realloc, das ist immer so.
    Am besten du kapselst das ganze in eine Funktion die das Zeichen anhängt und evtl. realloc aufruft.

    Oder du nimmst einfach nicht C, C ist nicht wirklich gut geeignet um mal kurz einen Scanner,Parser usw. zu bauen.



  • Mein Lexer erwartet dass der Quelltext bis zum Beendeten Parsen gültig und unverändert im Speicher bleibt.
    So speichere ich für jedes Token einfach einen Zeiger auf den Beginn des Lexemes + einen Zeiger auf das Ende (alternativ hält man die Länge).
    Wäre doch auch etwas für dich?



  • C ist bestens geeignet. mmap auf die Datei und dann den Trick von Ethon anwenden.
    Wenn dir das zu viel Arbeit ist, nimm flex+bison.



  • Verdammt daran hab ich gar nicht gedacht.
    So jetz nochmal das Beispiel, so wie Ethon sagte.
    Jetzt brauchst du nur noch den Sourcecode in den Speicher zu mappen und kannst später super Fehlermeldungen ausgeben.

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    #include <stdbool.h>
    bool is_wordchar(char c) {
    	return isalpha(c) || c == '_';
    }
    typedef struct {
    	const char *start;
    	const size_t len;
    } Token;
    Token token(const char *start, const size_t len) {
    	return (Token){.start=start, .len=len};
    } 
    Token token_str(const char *start) {
    	return (Token){.start=start, .len=strlen(start)};
    }
    void token_println(Token *t) {
    	printf("%.*s\n", t->len, t->start);
    }
    int main(void) {
    	char input[1024];
    	putchar('>');
    	fgets(input, sizeof(input), stdin);
    	for(size_t i=0; input[i] != '\0'; i++) {
    		Token tok;
    		switch(input[i]) {
    			case '\n':	tok = token_str("newline"); break;
    			case ' ':	tok = token_str("space"); break;
    			case '+':	tok = token_str("+"); break;
    			default:	//Wort
    						if(!is_wordchar(input[i])) {
    							printf("Scanner Fehler an Position %zu: unbekanntes Zeichen %c\n",i+1 , input[i]);
    							exit(EXIT_FAILURE);
    						}
    						size_t start = i;
    						for(;is_wordchar(input[i]); i++) {}
    						tok = token(input + start, i - start);
    						i--; //i zurücksetzen Stichwort: lookahead
    		}
    		token_println(&tok);
    	}
    	return EXIT_SUCCESS;
    }
    


  • Ethon schrieb:

    Mein Lexer erwartet dass der Quelltext bis zum Beendeten Parsen gültig und unverändert im Speicher bleibt.
    So speichere ich für jedes Token einfach einen Zeiger auf den Beginn des Lexemes + einen Zeiger auf das Ende (alternativ hält man die Länge).
    Wäre doch auch etwas für dich?

    "Lexemes"? Tut mir leid, ich verstehe nicht so ganz, wie du genau vorgehst.

    Wo speicherst du den Zeiger auf den Anfang und das Ende des Tokens?

    Meine Idee war, einen Mikrokernel zu programmieren, der skripte parsen kann.
    Naja, und wenn ich malloc benutze, dann heißt dass, dass ich ein rudimentäres malloc in meinen Microkernel implementieren müsste, was zwar kein Problem wäre, den Sache mit dem Mikrokernel unnötig ausweitet.

    Solche Sachen wie strlen() usw. sind schnell implementiert, aber malloc?

    Wie machen denn das andere "Low-Level" Konsolen, wie z. B. die Konsole von grub?



  • Achso ist das gemeint! Daran hatte ich bisher auch noch nicht gedacht! Eine sehr gute Idee.



  • Wenn es ganz einfach ist könntest du einen Syntax-Directed Interpreter benutzen.
    Dabei wird ein Token eingelesen und anhand von diesem läuft der Parser weiter und führt evtl. Aktionen aus.
    Das ist sehr simple und eingeschränkt reicht aber führ eine einfache Kommandozeile oder einen Taschenrechner.


Anmelden zum Antworten