Zeichenkette analysieren
-
Hallo, ich habe folgende Aufgabe:
Es soll der text einer Kopfzeile als Zeichenkette eingegeben werden und Name der Funktion, Zahl der Parameter und Parameternamen ausgegeben werden.
Die funktion gets soll verwendet werden.Bsp:
Eingabe: funk(a1,b1,u)
Ausgabe:
Name der Funktion: funk
1.Parameter: a1
2.Parameter: b1
3.Parameter: u
Zahl der Parameter: 3Ich habe erstmal grundlegende Probleme, die ich trotz Suche nicht finden kann.
Wie mache ich die Eingabe? (Woher weiß das Programm, wann alles eingegeben ist und wie trennt es die einzelnen Funktionen und Parameter)
-
Hallo!
Follfosten schrieb:
Wie mache ich die Eingabe? (Woher weiß das Programm, wann alles eingegeben ist und wie trennt es die einzelnen Funktionen und Parameter)
Die Funktion fgets sollte dafür ganz brauchbar sein, damit kannst du die Zeile
"funk(a1,b1,u)"
als Zeichenkette komplett in einen Puffer ( char Array ) einlesen.
Diesen Puffer klapperst du dann zeichenweise ab und teilst die Zeichenkette an den Positionen der definierten Trennzeichen auf. Hier wären die Trennzeichen z.B. ",\0(". Eventuell noch die Eingabe auf Fehler prüfen, etc.
-
Das heißt man gibt im Programm eine zeile ein, die mit Enter abgeschlossen wird und ich definiere im Programm, das bei Komma und Klammer aufgetrennt wird, richtig?
Ich hab jetzt meine Unterlagen und das I-net nach fgets und char Arrays abgeklappert und mir ist trotzdem nicht klar wie ich die Eingabe programmiere.
Es steht ja im Vorfeld nicht fest, wieviele Parameter die Funktion enthält bei diesen char-Geschichten wird doch aber immer die Anzahl in eckigen Klammern angegeben?!
-
Du musst doch nur deinen Buffer so groß wählen, dass er nie vollgeschrieben wird.
#include<stdio.h> #include<string.h> void main(void) { char buffer[1024]; char *pHelp; int i = 1; printf("Parameter eingeben:\n"); if(fgets(buffer, 256, stdin) != NULL) { // trennen: pHelp = strtok(buffer, ",()"); while(pHelp != NULL) { printf("Element %d: %s\n", i++, pHelp); pHelp = strtok(NULL, ",()"); } } }
Wenn ich jetzt eingebe
1,2(3)4,5
kommt als Ausgabe
Element 1: 1
Element 2: 2
Element 3: 3
Element 4: 4
Element 5: 5
-
Follfosten schrieb:
Das heißt man gibt im Programm eine zeile ein, die mit Enter abgeschlossen wird und ich definiere im Programm, das bei Komma und Klammer aufgetrennt wird, richtig?
Jepp.
Follfosten schrieb:
Ich hab jetzt meine Unterlagen und das I-net nach fgets und char Arrays abgeklappert und mir ist trotzdem nicht klar wie ich die Eingabe programmiere.
Es steht ja im Vorfeld nicht fest, wieviele Parameter die Funktion enthält bei diesen char-Geschichten wird doch aber immer die Anzahl in eckigen Klammern angegeben?!
Meistens ist es sinnvoll und ratsam, eine maximale Anzahl der Zeichen festzulegen, die eingegeben/eingelesen werden soll. Kannst du so machen:
Nimmst du ein char array als Puffer mit einer anmgemessenen Größe und liest mit fgets ein. Befindet sich das '\n' Zeichen im Puffer, dann wurde die Eingabe vollständig gelesen. Ist das '\n' Zeichen nicht im Puffer gelandet, dann wurden zu viele Zeichen eingegeben und die Eingabe wird als Fehler gewertet und verworfen ( eventuell auffordern neu einzugeben etc ).
Die andere Möglichkeit ist ( huhu nwp2 ):
Du liest mit fgets so lange ein, bis sich ein '\n' Zeichen im Puffer befindet,
oder bis kein RAM mehr verfügbar ist und dem System die Puste ausgehtund hängst die eingegebenen Teile an eine verkettete Liste an.
Ein Ansatz (Variante 1):
#include <stdio.h> #include <string.h> #define MY_BUFSIZE (BUFSIZ + 1) // Entfern das '\n' Zeichen, falls vorhanden, indem es mit einer // 0 überschrieben wird, die Zeichenkette s wird also verkürzt. // Gibt eine 1 zurück, wenn das '\n' Zeichen gefunden wurde, sonst 0. int remlf ( char* s ) { // remove linefeed. char* p = strrchr ( s, '\n' ); if ( p ) *p = 0; return p != NULL; // Wenn das '\n' Zeichen gefunden wurde, wird 1 zurückgegeben. } int main() { char buf[MY_BUFSIZE] = {0}; fgets ( buf, sizeof(buf), stdin ); if ( ferror(stdin) ) return 0; // Fehler. if ( !remlf(buf) ) puts("Fehler: Eingabe enthaelt zu viele Zeichen"); puts("Eingabe Ok!"); // Nun machst du was mit der Eingabe, also mit buf ... return 0; }
-
Follfosten schrieb:
(Woher weiß das Programm, wann alles eingegeben ist und wie trennt es die einzelnen Funktionen und Parameter)
mal dir 'nen zustandsautomaten auf, z.b:
state = funktionsbeginn funktionsbeginn: - if (buchstabe) -> ausgeben, state = funktion else -> error funktion: - if (buchstabe_oder_zahl) -> ausgeben else if (klammer_auf) -> state = parameterbeginn else -> error parameterbeginn: - if (buchstabe) -> ausgeben, state = parameter else -> error parameter: - if (buchstabe_oder_zahl) -> ausgeben else if (komma) -> state = parameterbeginn else if (klammer_zu) -> state = ende else -> error ende: ... fertig
^^btw, nur schnell hingehackt, wahrscheinlich voller fehler, aber das prinzip ist klar, ne?
ach ja, spaces musste ignorieren.
-
lex und yacc sind deine freunde...
-
Oder wenn du Lust auf Eigenbau hast machst du dir einen Parser mit einer Vorher/Jetzt Aktionstabelle. Diese Tabelle kann man z.B. mit einem Funktionszeigerarray erstellen.
Diese Tabelle definiert exakt, welche Funktion bei welchem Zeichen aufgerufen wird, in Abhängigkeit davon, was für ein Token zuvor eingelesen wurde.
Das hat Vorteile: Einige wenige bzw. so gut wie gar keine if/else Zweige, einfach erweiterbar/änderbar, es nimmt dir einen guten Anteil der Syntaxprüfung ab. Gute Übersicht bis ca. 20-30 Tokentypen.
-
Ok, das hat mir jetzt schonmal sehr weitergeholfen, werd mich weiter dran probieren...
-
Oki doki. Ich habe das in Code gefasst, wie das mit der Tabelle gemeint ist, ein Ansatz für nen Parser/Tokenizer, guckst du:
#include <stdio.h> #include <memory.h> #include <stdlib.h> // Private Bezeichner der Indizes für die Vorher/Jetzt Aktionstabelle. static enum action { eos, lbrack, rbrack, value, name, comma, ignore }; // Rückgabewert im Fehlerfall. #define err -1 // Diese Macros lassen sich vermeiden, siehe weiter unten bei der Funktion look(). #define iseos(x) x == 0 #define islbr(x) x == '(' #define isrbr(x) x == ')' #define isval(x) x >= '0' && x <= '9' || x == '.' // Beginn einer Zahl. #define isname(x) x >= 'a' && x <= 'z' || x >= 'A' && x <= 'Z' || x == '_' // Beginn eines Namens. #define namechar(x) isname(x) || x >= '0' && x <= '9' // Buchstabe eines Namens. #define iscomma(x) x == ',' #define isignore(x) x == ' ' static char* s = NULL; // Private Zeigerkopie der Eingabe. static int prev = eos, now = eos; // Vorher/Jetzt, private Indizes der Aktionstabelle. // Allgemeiner Standardfehler. Detailierte Fehlermeldungen/Behandlungen lassen sich // durch die Änderung der Aktionstabelle einrichten. int Err() { puts("Error!"); return err; } // Spezielle Fehlermeldung. // Zeigt, wie sich die Fehler in der Aktionstabelle benennen lassen können. int E1() // Zwei Kommas in Folge Fehler. { puts("E1: double comma trouble!"); return err; } int Comma() { puts ("Comma read."); s++; return comma; } // Geeignet für die Prüfung von z.B. Funktionsnamen. int Name() { printf ("Name: "); while ( namechar(*s) ) putchar(*s++); puts (""); return name; } // Geeignet für Wertprüfung z.B. mit strtod. int Value() { printf ("Value: "); while ( isval(*s) ) putchar(*s++); puts (""); return value; } int Ignore() { puts("Ignoring whites..."); while ( *s == ' ' ) s++; // Kandidat für while (isspace(*s)) return prev; } // Geeignet für Klammer-Syntax-Prüfung. int LBrack() { puts("Left bracket read."); s++; return lbrack; } // Geeignet für Klammer-Syntax-Prüfung. int RBrack() { puts("Right bracket read."); s++; return rbrack; } // Geeignet zum Abfangen von Funktionen, die nicht void sein dürfen. int Void() { s++; puts("Found empty brackets."); return now; // Wird hier ignoriert. } int Eos() // End of string ;;) { puts ("End of string - good bye!"); return eos; } // Die Vorher/Jetzt Aktionstabelle besteht aus Zeigern auf Funktionen. // Die Indizes Vorher/Jetzt wählen die entsprechende Funktion aus. // Du kannst direkt ablesen, was unter welcher Bedingung passiert. int (*Action[][7])()= { //-- prev --------------------------- now ------------------------- // 0 1 2 3 4 5 6 // Eos LBrack RBrack Value Name Comma Ignore /*0 Eos*/ Eos, LBrack, Err, Value, Name, Err, Ignore, /*1 LBr*/ Err, LBrack, Void, Value, Name, Err, Ignore, /*2 RBr*/ Eos, Err, RBrack, Err, Err, Comma, Ignore, /*3 Value*/ Eos, Err, RBrack, Err, Err, Comma, Ignore, /*4 Name*/ Eos, LBrack, RBrack, Err, Err, Comma, Ignore, /*5 Comma*/ Err, LBrack, Err, Value, Name, E1, Ignore }; // Diese Funktion zusammen mit den Macros kann ganz entfallen. int look() { if ( iseos(*s) ) return eos; if ( islbr(*s) ) return lbrack; if ( isrbr(*s) ) return rbrack; if ( isval(*s) ) return value; if ( isname(*s)) return name; // if ( namechar(x) ) wird hier nicht gebraucht. if ( iscomma(*s) ) return comma; if ( isignore(*s) ) return ignore; puts("Unbekanntes Zeichen!"); exit(1); } // Obige Funktion look(), zusammen mit den Macros lässt sich vermeiden, wenn man // alle chars von 0 - 255 als Index benutzt und in den zugehörigen action-index mit Hilfe // eines Arrays übersetzt(Codierung beachten!). Hier im Beispiel gekürzt und mit weiteren Zeichen // für Operatoren, Vorzeichen, etc. beispielhaft dargestellt: // (Der Wert null ist hier als Fehler gedacht.) /* const int tai[]= // Token action index :) { {eos},{null},{null},{null},{null},{null},{null},{ignore},{ignore},{ignore}, // 0-9 {ignore},{ignore},{ignore},{ignore},{null},{null},{null},{null},{null},{null}, // 10-19 // ... gekürzt ... {lbrack},{rbrack},{op},{sigPlus},{comma},{sigMinus},{value},{op},{value}, {value},// 40-49 {value},{value},{value},{value},{value}, {value},{value}, {value}, {op},{null}, // 50-59 {null},{null},{null},{null},{null},{name},{name},{name},{name},{name},// 60-69 {name},{name},{name},{name},{name},{name},{name},{name},{name},{name},// 70-79 {name},{name},{name},{name},{name},{name},{name},{name},{name},{name},// 80-89 {name},{null},{null},{null},{op}, {name},{null},{name},{name},{name},// 90-99 {name},{name},{name},{name},{name},{name},{name},{name},{name},{name},// 100-109 {name},{name},{name},{name},{name},{name},{name},{name},{name},{name},// 110-119 {name},{name},{name},{null},{null},{null},{null},{null},{null},{null},// 120-129 {null},{null},{null},{null},{null},{null},{null},{null},{null},{null},// 130-139 // ... gekürzt ... {null},{null},{null},{null},{null},{null},{null},{null},{null},{null},// 240-249 {null},{null},{null},{null},{null},{null} // 250-255 }; Man kann also die Aktionstabelle erweitern/ändern und weitere Funktionen und Regeln hizufügen, um den Parser weiter auszubauen. Die Macros oben für die Klassifizierung der Zeichen/Zeichenerkennung, können komplett entfallen, sowie die Funktion look() und die Variable 'static int now'. In der parse Funktion ersetzt man dann die Variable 'now' durch den 'Token action index' tai: prev = Action[prev][tai[(unsigned)*s]](); */ int parse ( char* input ) { s = input, now = prev = eos; Ignore(); // Eventuell vorkommende, führende Leerzeichen ignorieren. do { now = look(); prev = Action[prev][now](); }while ( prev > 0 ); if ( prev == -1 ) return 1; // Fehler return 0; // Ok. } void print_line() { char buf[81] = {0}; memset ( buf, '-', 80 ); printf (buf); } int main() { char* test = "Funktionsname ( abc, def, 1.12 )"; // Simulierte Eingabe. if ( 0 == parse ( test ) ) printf ("First stage passed for %s\n", test ); else printf ("First stage failed for %s\n", test ); print_line(); test = " demo_double_comma_trouble_error ( 08.15,, )"; // Einsatz der E1 Funktion zeigen. if ( 0 == parse ( test )) printf ("First stage passed for %s\n", test ); else printf ("First stage failed for %s\n", test ); return 0; }
Gruß,
B.B.