Interaktion mit dem Benutzer
-
Hi!
Aktuell verwende ich zur Interaktion mit dem Benutzer (oder einer Pipe) innerhalb der C-Konsole folgendes Konstrukt:
enum { SIZE = 1 << 16 }; char buffer[SIZE]; // 64K char* current; // Returns a pointer to the beginning of input or NULL char* GetInput() { current = buffer; if (fgets(buffer, SIZE, stdin) == NULL) { if (feof(stdin)) { return NULL; // End-of-file } fprintf(stderr, "Failed to get input from stream\n"); fflush(stderr); exit(EXIT_FAILURE); } return current; } // Returns a pointer to the next token or NULL const char* GetToken() { while (isspace(*current) != 0) { ++current; } if (*current == 0) { return NULL; } const char* token = current; while (isspace(*current) == 0) { ++current; } *current++ = 0; return token; }
strtok() kommt nicht in Frage, da nicht threadsafe. Gibts vielleicht ein noch einfacheres C-Pattern, das ähnlich funktioniert?
Danke und Gruß
-
(s)scanf? Wieso nicht gleich so einlesen, wie man es haben möchte, anstatt alles von Hand zu zerlegen?
-
SeppJ schrieb:
(s)scanf? Wieso nicht gleich so einlesen, wie man es haben möchte, anstatt alles von Hand zu zerlegen?
Hi!
Sorry, habe das Problem nicht genau beschrieben. Das Tokenizing wird innerhalb einer Finite-State-Machine implementiert:
Beispiel:
void FSM() { const char* token; int state = 0; while ((token = GetToken()) != NULL) { switch (state) { case 0: if (strcmp("A", token) == 0) { state = 1; } break; case 1: if (strcmp("B", token) == 0) { state = 2; } break; // ... default: assert(false); } // switch } // while }
Wie sollte sich (s)scanf hier einsetzen lassen?
-
Kannst du das mal insgesamt genauer beschreiben? Sind die Tokens nur einen Buchstaben lang? Falls ja, warum dann überhaupt Zeichenkettenverarbeitung? Sag mal genau an, was du für eine Art von Eingabe hast und was du dir als Ausgabe wünscht, dann schlagen wir dir einen Verarbeitungsschritt vor.
Funktioniert dein Code überhaupt? Er arbeitet im Prinzip doch so wie strtok, insbesondere ist er ebenfalls nicht threadsicher.
assert für Nutzereingaben (? es sind doch Nutzereingaben, oder?) ist keine gute Idee.
-
Es handelt sich hierbei im das Universal Chess Interface (UCI) Protokoll. Hier kommuniziert eine GUI mit einer Konsolenanwendung.
Hier mal ein paar Beispiele in Backus-Naur-Form:
// position [startpos | fen <fenstring>] moves <move1> .... <movei>
oder
// wtime <x> btime <x> winc <x> binc <x> movestogo <x> depth <x> movetime <x> infinite
Ich wollte im ersten Schritt mit fgets() von stdin einlesen und im zweiten Schritt die Tokens mittels FSM verarbeiten.
FSM und C-Funktionen sind hier meine Bedingungen.
-
Eine FSM ist in diesem speziellen Fall vielleicht "Overkill", aber eine nette Übung. Außerdem möchte ich mich nur aus dem C99 Standard bedienen.
-
char s[100]; while( 1==scanf("%99s",s) ) { ... }
-
Beispielsweise so, falls Zeilenumbrüche eine Rolle spielen:
https://ideone.com/2SD0ouOder noch einfacher, wenn sie es nicht tun (nichts in deinem Format deutet darauf hin):
https://ideone.com/AJprCr
-
Danke, das schaut gut aus!
-
Tomahawk schrieb:
Beispiel:
[...] while ((token = GetToken()) != NULL) { switch (state) { case 0: if (strcmp("A", token) == 0) { [...]
Wenn ich Deine GetToken - Routine richtig verstehe, ist nicht sichergestellt, dass token nullterminiert ist - dann sollte strcmp doch nicht das richtige (erwartete) Ergebnis liefern, oder?
-
Belli schrieb:
Tomahawk schrieb:
Beispiel:
[...] while ((token = GetToken()) != NULL) { switch (state) { case 0: if (strcmp("A", token) == 0) { [...]
Wenn ich Deine GetToken - Routine richtig verstehe, ist nicht sichergestellt, dass token nullterminiert ist - dann sollte strcmp doch nicht das richtige (erwartete) Ergebnis liefern, oder?
Und was ist mit Zeile 38?
-
Tomahawk schrieb:
Und was ist mit Zeile 38?
Passt schon. Ich hoffe nur, dir ist klar, dass deine Funktion wirklich nur eine Kopie von strtok ist, alle Nachteile inbegriffen, die du eigentlich loswerden wolltest:
strtok() kommt nicht in Frage, da nicht threadsafe.
Deines erst recht.
-
Tomahawk schrieb:
Und was ist mit Zeile 38?
Jo, hatt ich übersehen ...
-
SeppJ schrieb:
Tomahawk schrieb:
Und was ist mit Zeile 38?
Passt schon. Ich hoffe nur, dir ist klar, dass deine Funktion wirklich nur eine Kopie von strtok ist, alle Nachteile inbegriffen, die du eigentlich loswerden wolltest:
strtok() kommt nicht in Frage, da nicht threadsafe.
Deines erst recht.
Ja, das stimmt.
Lässt sich aber schnell ändern:
struct { char* current; char buffer[SIZE]; }
Und dann als Referenz übergeben.
Aber der Ansatz mit scanf() gefällt mir am besten. Einlesen und Tokenizen in Einem. Ich schätze mal da gibts keinen Haken?
-
Tomahawk schrieb:
Aber der Ansatz mit scanf() gefällt mir am besten. Einlesen und Tokenizen in Einem. Ich schätze mal da gibts keinen Haken?
Es gibt nun eine maximale Tokenlänge (die Größe des an scanf übergebenen Feldes). Dieser Nachteil wiegt sicher nicht schwer, aber ist erwähnenswert.
-
Folgender Code ist zwar nicht ganz threadsafe (es wird auf stdin operiert), sollte aber soweit ganz gut zum einfachen Tokenizen geeignet sein.
#include <ctype.h> // isspace #include <stdio.h> // fgets #include <string.h> // strlen class Input { public: Input() { m_current = m_buffer; } // Returns true if successful bool GetInput() { m_current = m_buffer; if (fgets(m_buffer, SIZE, stdin) == NULL) { return false; // End-of-file or error } // Remove newline m_buffer[strlen(m_buffer) - 1] = 0; return true; } // Returns a pointer to the next token or NULL const char* GetToken() { while (isspace(*m_current) != 0) { ++m_current; } if (*m_current == 0) { return NULL; } const char* token = m_current; while (isspace(*m_current) == 0) { ++m_current; } *m_current++ = 0; return token; } private: enum { SIZE = 1 << 16 }; char m_buffer[SIZE]; // 64K char* m_current; };
-
Was soll das denn sein? Einerseits programmiert wie in C, aber andererseits ein paar Syntaxmittel benutzt, die es nur in C++ gibt. Für wen soll das denn gut sein?
Ein paar Sachen, über die ich nicht glücklich bin:
-Umgang mit Dateien, die nicht auf newline enden
-Warum muss eigentlich eine Datenzerlegungsklasse selber lesen? Trennung von Zuständigkeiten!
-
SeppJ schrieb:
Ein paar Sachen, über die ich nicht glücklich bin:
-Umgang mit Dateien, die nicht auf newline enden
-Warum muss eigentlich eine Datenzerlegungsklasse selber lesen? Trennung von Zuständigkeiten!Stimmt Beides - kein Einspruch!
SeppJ schrieb:
Was soll das denn sein? Einerseits programmiert wie in C, aber andererseits ein paar Syntaxmittel benutzt, die es nur in C++ gibt. Für wen soll das denn gut sein?
Ich verwende isspace und char-array (lassen wir fgets außen vor wegen Trennung der Zuständigkeiten).
Was ist daran nicht OK?
-
Tomahawk schrieb:
Ich verwende isspace und char-array (lassen wir fgets außen vor wegen Trennung der Zuständigkeiten).
Was ist daran nicht OK?
Das class.
Wenn du C machst, kompiliere doch bitte auch mit einem C-Compiler.
-
Nachfolgende Variante eines Tokenizer ersetzt alle spaces in buffer durch 0 und trägt die Anfangsadressen von jedem Token in buffer in die Token List ein.
#include <assert.h> #include <ctype.h> // isspace #include <stdio.h> // NULL struct TokenInfo { char* token; }; // Returns a pointer to the last token + 1 TokenInfo* Tokenize(char* buffer, TokenInfo* token_list) { assert(buffer != NULL); assert(token_list != NULL); bool found_token = false; for (char* current = buffer; *current != 0; ++current) { if (found_token) { if (isspace(*current)) { *current = 0; found_token = false; } } else { if (isspace(*current)) { *current = 0; } else { token_list++->token = current; found_token = true; } } } // for return token_list; }
Die Interaktion in der Konsole oder einer Pipe sieht dann ihn etwa wie folgt aus:
#include <stdio.h> // fgets, stdin, NULL void Parse() { char buffer[1024]; while (fgets(buffer, 1024, stdin) != NULL) { TokenInfo token_list[1024]; // HACK TokenInfo* end = Tokenize(buffer, token_list); for (TokenInfo* current = token_list; current < end; ++current) { const char* token = current->token; // Do something with "token" } } // while // End-of-file or error }
Der Datentyp TokenInfo soll nur verhindern, dass man direkt mit Doppelzeigern arbeiten muss (tut man so zwar auch, aber nicht so offensichtlich).
Im Grunde ist dieser Algorithmus einen Ticken weniger performant als der Vorgänger, da man zusätzlich noch eine Token List benötigt.