Hallo Leute, ich versuche gerade an einen µC seriell eine ganz einfache Steuerbefehlskette zu übersenden und scheitere gerade am sehr simplen Parser! Ich will an einen µC einen RGBW wert übermitteln um eben drei Konstantstromquellen entsprechend mit PWM zu dimmen. Übertragen werden sollen immer vier zahlen von 0-255, stellvertretend für den PWM Wert, mit einem Komma separiert und mit * startend und # endend. z.B. so: *127,255,001,111# Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an der richtigen stelle stehen. Das klappt auch! Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig per "atoi" zu extrahieren. Aus dem Beispiel *127,255,001,111# extrahiert mir mein Progrämmchen 127,255127, 1255127, -667894569 statt 127,255,1,111 :-( Ich hab mir dazu das im Anhang angehängte "Demo" geschrieben um zu prüfen, ob es das so tut, bevor ich es auf den µC portieren kann. Es wäre super, wenn sich jemand geübteres als ich sich das mal ansehen könnte und mir einen Tipp oder einen Wink mit dem Zaunpfahl geben könnte, denn ich sehe den Fehler einfach nicht :-(
Wie sind Zeichenketten in C aufgebaut? Wie viel Platz ist in Rbuffer, Gbuffer etc.? Was parst/wandelt atoi dann u.U. alles bzw. was fehlt? Hint: Welches Verhalten hat atoi, wenn nach einer Zahl/Ziffer noch andere Zeichen im String enthalten sind? Sind die Buffer dann noch nötig?
:
Bearbeitet durch User
Arc N. schrieb: > Wie sind Zeichenketten in C aufgebaut? ? als Array aus Chars? Arc N. schrieb: > Wie viel Platz ist in Rbuffer, Gbuffer etc.? Die R/G/B/W Buffer sind für drei zeichen gedacht eben von 000 bis theoretisch 999. Den Wertebereich muss ich später noch abfangen! Ich dachte erst, ich hätte Probleme mit "001" da zwei vorgestellte Nullen da sind, aber der Fehler passiert ja schon mit 255. Arc N. schrieb: > Was parst/wandelt atoi dann u.U. alles bzw. was fehlt? Auch das weiß ich nicht :-( Ich denke ich muss noch viel lernen!
Folio schrieb: > Arc N. schrieb: >> Wie sind Zeichenketten in C aufgebaut? > ? als Array aus Chars? Ja, mit ... ... am Ende (siehe Post über diesem ;)) > Arc N. schrieb: >> Was parst/wandelt atoi dann u.U. alles bzw. was fehlt? > > Auch das weiß ich nicht :-( Ich denke ich muss noch viel lernen! http://www.cplusplus.com/reference/cstdlib/atoi/
Folio schrieb: > Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an > der richtigen stelle stehen. Das klappt auch! > > Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig > per "atoi" zu extrahieren. Dann extrahiere sie doch ganz einfach OHNE "atoi". Sowas sollte ja wirklich kein Thema sein. Lade dir einfach die Lernbetty herunter und guck dort in conv.c hinein, falls du so etwas nicht selber aus dem Ärmel schütteln kannst. Und wer sowas nicht aus eigener Kraft kann und dennoch die Nase über die olle Lernbetty rümpft, dem ist nicht zu helfen. W.S.
Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0'). Einfacher kannst du aber atoi, der eh beim ersten ungültigen Zeichen aufhört, direkt auf den String loslassen:
1 | int parse(char *str) |
2 | {
|
3 | if (strlen(str) > 16 |
4 | && str[0] == '*' && str[16] == '#' |
5 | && str[4] == ',' && str[8] == ',' && str[12] == ',') |
6 | {
|
7 | int r = atoi(str + 1); |
8 | int g = atoi(str + 5); |
9 | int b = aoti(str + 9); |
10 | int w = atoi(str + 13); |
11 | // mach was mit r,g,b,w
|
12 | }
|
13 | ...
|
14 | }
|
Alternativ kannst du dir auch mal sscanf anschauen.
foobar schrieb: > Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins > größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0'). Warum sagt man das denn nicht einfach. Ich bin noch absoluter Anfänger :-( Ich kämpfe noch mit so vielen Baustellen Zeitgleich und nur kryptische Kommentare. Ich hab mir sogar mein eigenes "atoi" implementiert, das das selbe Ergebnis lieferte wie das echte :-/ ich war schon echt am verzweifeln. Wie gesagt, ich muss noch viel lernen! Danke!
Folio schrieb: > foobar schrieb: >> Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins >> größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0'). > > Warum sagt man das denn nicht einfach. Ich bin noch absoluter Anfänger > :-( Ich kämpfe noch mit so vielen Baustellen Zeitgleich und nur > kryptische Kommentare. Ich hab mir sogar mein eigenes "atoi" > implementiert, das das selbe Ergebnis lieferte wie das echte :-/ ich war > schon echt am verzweifeln. > > Wie gesagt, ich muss noch viel lernen! > Danke! Am besten lernt man, wenn man selber drauf kommt und das geht am besten, wenn man von jemand auf das richtige Gleis gesetzt wird, vielleicht sogar ganz dicht ans Ziel. Dann den letzten Schritt zum Erfolgserlebnis selber machen. Wenn man hier also solche Antworten wie oben bekommt, dann geschieht das in bester Absicht.
> Warum sagt man das denn nicht einfach.
Du wolltest nen Tipp, den haben sie gegeben. Ich hab dann den Zaunpfahl
rausgeholt ;-)
Dein Code macht alles mögliche, aber nichts sinnvolles. Am bequemsten kannt Du mit sscanf() die 4 Zahlen einlesen. Du kannst aber auch in einer Schleife (0..3) mit strtok() den String zerlegen und mit atoi() dann einlesen. strtok() ersetzt den Delimiter "," durch "\0" und gibt für atoi() den Pointer auf den Teilstring zurück. sscanf() ist eine mächtige und sehr komfortable Funktion. Es lohnt sich daher, sich damit zu beschäftigen und die Doku dazu zu lesen (gibt auch Tutorials dazu in deutsch).
:
Bearbeitet durch User
strtok ist doch gar nicht nötig, da atoi auch beim ',' aufhört.
Hinweis: strtok sollte man aus seinem Repertoir streichen! Das ist eines der schwarzen Schafe im C-Standard. Zum einen hat sie verborgenen State und zum anderen ändert sie den übergebenen String (das "const" beim ersten Parameter ist nicht nur einfach vergessen worden). Am besten einfach ignorieren ;-)
atoi braucht aber den Zeiger auf die nächste Zahl. Sich darauf zu verlassen, daß immer 4 Bytes Abstand sind, kann einem später auf die Füße fallen.
foobar schrieb: > strtok sollte man aus seinem Repertoir streichen! Es macht allerdings das Parsen von Kommandozeilen sehr bequem. Ich versuche erstmal Standardfunktionen zu verwenden, ehe ich mir was selber schreibe.
> Sich darauf zu verlassen, daß immer 4 Bytes Abstand sind
Hat ja keiner gesagt, dass die Aufgabenstellung besonders elegant wäre
;-) Zumindest wird vorher geprüft, dass etwas halbwegs sinnvolles
vorhanden ist und das Programm nicht abstürzt. Besser geht immer -
sscanf, strtoul, handgedengelt, ...
>> strtok sollte man aus seinem Repertoir streichen! > > Es macht allerdings das Parsen von Kommandozeilen sehr bequem. Ja, ist ärgerlich: die Funktionalität kann man sehr gut gebrauchen. Leider hängen so viele Fallstricke dran, dass man besser die Finger von läßt. Z.B. das Parsen von Kommandozeilen: man hat den Parser fertig und er scheint zu funktionieren. Bis man die Funktion mit einem Konstantstring (im .rodata) aufruft - Peng. Dann splittet man z.B. bei Leerstellen und übergibt das abgetrennte Wort an eine Funktion - wollen wir mal hoffen, dass die nicht in ihren Tiefen auch irgendwo strtok benutzt denn sonst fängt meine Schleife an zu spinnen. Ich bleib dabei: besser nicht benutzen.
Okay, jetzt müsst ihr euch doch nicht meinetwegen über Bibliotheken und deren Funktionen streiten! Ich dachte nur, das ich darauf so gut es eben geht verzichten sollte, wenn es für eine µC gerichtetes Programm werden soll. (Ich hatte noch im Hinterkopf: ein mal printf und das Flash ist voll ODER: Es ist nicht implementiert). Darum habe ich auch ein eigenes atoi implementiert... aber egal, jetzt war es halt nur eine Übung für mich ;) Wie würdet ihr das denn sonst machen? Wie gesagt, ich bin noch absoluter Neuling und noch ordentlich grün hinter den Ohren. Mein momentanes Ziel ist es aktuell nur ein Frame aus 4 Zahlen und ein paar Trenn/Sonderzeichen zu übertragen. Hat vielleicht sonst jemand ein minimales Beispiel für mich, an dem ich mich orientieren kann? Aja, falls von belangen: Ich verwende aktuell so ein spottgünstiges STM32-Arduino Clone ding (für unter 2€!) und ja auch den Arduino kompatiblen Bootloader von Roger Clark. Ob ich aber später das Arduinozeugs weiter benutzen will ist noch nicht sicher! Aktuell benutze ich es nur aus Bequemlichkeit!
Folio schrieb: > (Ich hatte noch im Hinterkopf: ein mal printf und das Flash ist > voll ODER: Es ist nicht implementiert). Nun, seit 1980 hat sich aber einiges getan. Die Compiler habe sich deutlich weiterentwickelt und auch die MCs haben erheblich mehr Flash und RAM. Ein printf, scanf oder float bringt kaum noch einen MC zum Schwitzen. Heutzutage legt man mehr Wert darauf, daß Programme schnell entwickelt werden können und auch zuverlässig, gut lesbar, wartbar und erweiterbar sind. Daher ist es besser, wenn man erprobte Libs verwendet, anstatt das Fahrrad immer wieder neu zu erfinden.
Folio schrieb: > Wie würdet ihr das denn sonst machen? > Wie gesagt, ich bin noch absoluter Neuling und noch ordentlich grün > hinter den Ohren. Mein momentanes Ziel ist es aktuell nur ein Frame aus > 4 Zahlen und ein paar Trenn/Sonderzeichen zu übertragen. Also ich kann Herangehensweisen nicht leiden, die von festen Positionen für Textzeichen ausgehen, so wie du das gemacht hast. So etwas ist sehr steif und versagt völlig, wenn da irgendwo mal das Format nicht ganz exakt stimmt. Es wäre ja in deinem Beispiel auch logisch nicht zu verstehen, wenn man für schwarz unbedingt *000,000,000,000# schreiben müßte. Weitaus bequemer ist es, wenn die einzelnen Einträge formatfrei gehalten wären, also *0 127 24 3# anstelle *000,127,024,003#, dann hättest du erstens weniger Aufwand beim Erzeugen der Zeilen und zweitens eine Flexibilität, die ein späteres Ändern viel leichter macht. Mein Rat mit der Lernbetty war übrigens sehr ernst gemeint. Dort sind Input-Konvertierungen drin, die mit formatfreien Zeilen zurechtkommen. Warum? Weil man ja auch damit rechnen muß, daß man manuell eingetippte Kommandozeilen auszuwerten hat. Wer da auf feste Zahlenpositionen setzt, macht sich oder anderen Streß. Beim cmd.c in der Lernbetty sähe das so aus: char* Cpt; Cpt = Pzeile; if (match("*",&Cpt)) { rot = Long_In(&Cpt); gruen = Long_In(&Cpt); blau = Long_In(&Cpt); W = Long_In(&Cpt); if (!match("#",&Cpt)) SagFormatfehler(); } Allerdings erinnere ich mich dunkel, daß abschließende Trennzeichen bei Zahleneingaben nicht überlesen werden, sondern ggf. getrennt abgefragt oder übergangen werden müßten - außer Leerzeichen. Naja, und ob du nun für deine Zwecke ein Long_In(..) brauchst oder mit einer geringeren Datenbreite auskommst, ist dein Geschmack. Bei 32 Bit Controllern wie der Lernbetty ist aber long quasi das natürlichste. Ein ganz anderes Verfahren benutze ich, wenn es wirklich NUR um serielle Kommunikation zwischen 2 Maschinen geht. Die brauchen keine visuelle Rückmeldungen und keine Zeilen. Also ist es dort am besten, nur atomare Daten zu übertragen - dies aber wieder formatfrei. Auf dein Beispiel bezogen sähe sowas dann so aus: 0R127G24B2W1K ..und so weiter. Ohne Leerzeichen (die würden überlesen) und ohne CRLF, das würde auch überlesen. Maschinen brauchen es nicht leserlich, sondern nur sparsam und leicht zu dekodieren. Jede Einstellung wird mit dem numerischen Wert begonnen und durch einen entsprechenden Buchstaben abgeschlossen, der den Wert übernimmt und dann den Akku für das Auflaufen des nächsten Wertes löscht. und die Interpretation ist: 0R Rotwert=0 setzen 127G Grünwert=127 setzen 24B Blauwert setzen 2W W_wert =2 setzen 1K Konstantstromquelle 1 auf die gesetzten Werte einstellen Und ob man da nun 0R oder bloß R oder 000000000000R schreibt, ist egal. Das Angenehme bei diesem Verfahren ist, daß man praktisch keine Eingabezeile aufbauen muß. Man kann für den Dekoder einfach die Zeichen nehmen, so wie sie von der Seriellen herkommen. Das ist ne andere Nummer als das, was du machst: Folio schrieb: > Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an > der richtigen stelle stehen. Das klappt auch! > Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig > per "atoi" zu extrahieren. eben. Zuerst mußt du die Zeile aufbauen, um alles an festgelegte Positionen zu bringen und dann klappt dein atoi trotzdem nicht. W.S.
Peter D. schrieb: > Heutzutage legt man mehr Wert darauf, daß Programme schnell entwickelt > werden können und auch zuverlässig, gut lesbar, wartbar und erweiterbar > sind. Daher ist es besser, wenn man erprobte Libs verwendet, anstatt das > Fahrrad immer wieder neu zu erfinden. Nö. So etwas würde nur dann wirklich Sinn machen, wenn die genannten "erprobten" Libs auch wirklich zum angezielten Einsatzzweck passen. Daß das in dem vorliegenden Falle eher nicht stimmt, hast du ja hier lesen können. Abgesehen davon ist auch das Exposé des TO nicht wirklich zweckmäßig. Wichtig ist nur eines: daß man als Mikrocontroller-Programmierer mit der Zeit sich ein Portfolio an hardwareunabhängigen Modul zulegt, die auf die Bedingungen des µC-Bereiches passen. Und dazu zählt printf und Konsorten eben nicht, weil das eben immer einen Formatzeilen-Interpreter bedeutet, der zur Laufzeit Dinge tut, die man eigentlich bereits zur Compilationszeit erledigt haben könnte. Sowas ist nur der bellenden Faulheit und gedankenlosem Umgang mit den Ressourcen geschuldet. Abgesehen davon muß man bei printf auch darauf achten, was für Ausdrücke die betreffende Variante überhaupt kennt. Die Varianten für µC sind oftmals gekürzte Implementationen, weil man hier mehr Wert darauf legt, das Ganze in den Flash zu kriegen. W.S.
W.S. schrieb: > Also ich kann Herangehensweisen nicht leiden, die von festen Positionen > für Textzeichen ausgehen, so wie du das gemacht hast. auch wenn ich deine Sichtweise anerkenne, für den Anfang hatte der TO doch gut überlegt, wenn schon die Zeile parsen doch dann auf das erste Vorkommen einer Ziffer (ggffs. erweitert um +-) *1.) als Start Pointer zu Atoi. Dann die Länge feststellen, also weiterparsen bis keine Ziffer mehr kommt und wenn dann noch nicht \0 oder Ende vom String erreicht ist weitersuchen, bis zum nächsten Vorkommen von *1.) solange bis alle Zahlen gefunden sind, der String beendet ist. Ist eine schöne Lernübung.
W.S. schrieb: > char* Cpt; > Cpt = Pzeile; > if (match("*",&Cpt)) > { rot = Long_In(&Cpt); > gruen = Long_In(&Cpt); > blau = Long_In(&Cpt); > W = Long_In(&Cpt); > if (!match("#",&Cpt)) SagFormatfehler(); > } Es ist weitgehend sinnfrei, Beispiele zu posten, die weder Standardlibs verwenden, noch den Quelltext der Funktionen beinhalten.
Peter D. schrieb: > noch den Quelltext der Funktionen beinhalten. Aber das verwendet doch die "Lernbetty"! Das ist quasi der C-Standardcode überhaupt, auch wenn die ISO den immer noch nicht abgesegnet hat.
Ob das jetzt gut oder schlecht ist, wie die Nachricht des TO aufgebaut ist, sei mal dahingestellt. Ohne strtok, atoi & co. koennte das vielleicht so aussehen (Fehlerbehandlung hab ich mal weggelassen):
1 | #include <stdint.h> |
2 | #include <stdio.h> |
3 | #include <inttypes.h> |
4 | |
5 | |
6 | uint16_t ascii_to_u16(char* str); |
7 | void parse(char* str, uint16_t* buff); |
8 | |
9 | |
10 | int main(void) |
11 | {
|
12 | char msg[] = "*127,255,001,111#"; |
13 | uint16_t nums[4] = {0}; |
14 | |
15 | parse(msg, nums); |
16 | |
17 | for (int i = 0; i < 4; i++) { |
18 | printf("Num[%i]: %" PRIu16 "\n", i, nums[i]); |
19 | }
|
20 | |
21 | return 0; |
22 | }
|
23 | |
24 | |
25 | uint16_t ascii_to_u16(char* str) |
26 | {
|
27 | uint16_t ret_val = (str[0] - '0') * 100 + (str[1] - '0') * 10 + (str[2] - '0'); |
28 | |
29 | return ret_val; |
30 | }
|
31 | |
32 | |
33 | void parse(char* str, uint16_t* buff) |
34 | {
|
35 | buff[0] = ascii_to_u16(&str[1]); |
36 | buff[1] = ascii_to_u16(&str[5]); |
37 | buff[2] = ascii_to_u16(&str[9]); |
38 | buff[3] = ascii_to_u16(&str[13]); |
39 | }
|
1 | $ gcc -std=c99 -Wall -Wextra -o main main.c && ./main |
2 | Num[0]: 127 |
3 | Num[1]: 255 |
4 | Num[2]: 1 |
5 | Num[3]: 111 |
Das ist natuerlich nicht das optimum, da Fehlerbehandlung fehlt, negative Zahlen nicht beruecksichtigt werden, und die Zahl maximal 3 Ziffern haben darf. Funktioniert also nur fuer 000 bis 999. Das koennte aber fuer den TO vielleicht trotzdem ein Anfang sein.
Kaj schrieb: > void parse(char* str, uint16_t* buff); Tja, das ist zu wenig. Dein parse kann keinen String parsen. Stattdessen ist es nur eine Zusammenfassung von 4x ascii_to_u16(..), die mit jeweils einem festen Zeiger aufgerufen werden. Eine parse-Funktion muß anders aussehen. Etwa so: my_int_typ parse(char** posinstring); Das deshalb, weil parse ja den String durchsuchen soll, dabei sowas wie Leerzeichen übergehen, dann die Zahl konvertieren und anschließend den Zeiger im String auf die Stelle dahinter setzen. Sowas ist "parsen". Peter D. schrieb: > Es ist weitgehend sinnfrei, Nee Peter, dein Beitrag war herzlich sinnlos, da du ja keinen positiven Beitrag leisten, sondern nur mäkeln wolltest. Mein Beitrag war sehr sinnvoll, da er einen Eindruck von der Verwendung echt parsender Eingabe-Konvertierungen gibt. Genau DAS war das Anliegen. Du hättest es erkennen können, wenn du nur korrekt gelesen hättest. Die Betonung liegt auf VERWENDUNG. Wie das modulintern geht, kann ja jeder nachlesen, die Quelle ist genannt. Und Rufus liegt mit seiner überflüssigen Bemerkung mal wieder grundfalsch (wie so oft in letzter Zeit), denn meine Routinen in der Lernbetty sind im Gegensatz zu dem Standard-Zeugs, was bei den meisten C-Compilern so dabei ist, eben nicht Standard, sondern an die Verhältnisse in einem MIKROCONTROLLER angepaßt. In diesem Falle sogar hardwareunabhängig und dennoch effektiver als sowas wie atoi, denn das kann ja nicht einmal das Ende der Zahl im String zurückliefern. Ich erinnere mich an die Bemerkungen eines echten Programmierer-Idioten vor langer Zeit, der sich darüber aufgeregt hat, daß man bei den kleinen PIC16 ja nur 4 Plätze im Stack hat - wo er doch bei seinem PC ja mindestens 16K oder noch mehr an Stack hätte, weshalb also diese Chips ein völlig ungeeignetes zu nix zu gebrauchendes Zeugs seien. So ungefähr kommen mir Bemerkungen über Standard-Funktionen in C auf dem µC vor. W.S.
Nochwas: War das nicht so, daß atoi als Argument ein "const char* string" haben will? Was machen denn da die AVR-Anwender? W.S.
W.S. schrieb: > Nochwas: > War das nicht so, daß atoi als Argument ein "const char* string" haben > will? > Was machen denn da die AVR-Anwender? > > W.S. Ja, atoi hat einen "const char*" Parameter. Wo siehst du da ein Problem? Bei all deinen "selbstbewussten" Beiträgen können wir doch sicher erwarten, dass du "const" verstanden hast.
W.S. schrieb: > War das nicht so, daß atoi als Argument ein "const char* string" haben > will? Erkundige Dich doch erstmal, was "const" in einem Funktionsparameter bedeutet. > Was machen denn da die AVR-Anwender? Dasselbe, was die anderen Anwender auch machen.
:
Bearbeitet durch Moderator
W.S. schrieb: > Nö. So etwas würde nur dann wirklich Sinn machen, wenn die genannten > "erprobten" Libs auch wirklich zum angezielten Einsatzzweck passen. Daß > das in dem vorliegenden Falle eher nicht stimmt, hast du ja hier lesen > können. Warum soll man auf dem AVR kein sscanf verwenden dürfen? Ich habs mal compiliert, das Programm ist ~2kB groß, paßt also bequem in einen kleinen ATtiny85. Man muß heute nicht mehr den Stand von 1980 (P8751: 4kB OTP, 0,1kB RAM) als Maßstab nehmen.
*127,255,001,111# Kaj schrieb: > void parse(char* str, uint16_t* buff) > { > buff[0] = ascii_to_u16(&str[1]); > buff[1] = ascii_to_u16(&str[5]); > buff[2] = ascii_to_u16(&str[9]); > buff[3] = ascii_to_u16(&str[13]); > } das klappt auch nur wenn die zahlen immer 3 byte lang sind also *001,002,003,004# ich würde vlt die trenner nullen *127\0255\0001\0111\0 und dabei die pointer merken genullt sind sie ja dann alle schon
foobar schrieb: >>> strtok sollte man aus seinem Repertoir streichen! > Ich bleib dabei: besser nicht benutzen. Nun ja pauschal kann das nicht sagen... Z.Bsp wenn man solche Funktionen die den String verändern nur eine Kopie übergibt. Bleibt nur noch der versteckte State.. Aber man solche Funktionen auch selbst sicher bauen...
Wem das smarte strtok() zu "gefährlich" ist, der kann auch mit strchr() nach '*' bzw. ',' suchen lassen und dann atoi() den Pointer+1 übergeben. Ich verwende sscanf() auch eher selten. Es ist recht unflexibel, wenn die Daten verschiedene Formate haben können.
Man könnte mit einer Funktion wie dieser integer parsen:
1 | bool nintp(const char**restrict s, const char* end, int*restrict state){ |
2 | const char* it = *s; |
3 | int x = *state; |
4 | for(;it < end && *it>='0' && *it<='9'; it++) |
5 | x = x * 10 + (it[0] - '0'); |
6 | bool res = *s == it; |
7 | *s = it; |
8 | *state = x; |
9 | return res; |
10 | }
|
Aber sobald man das in einer state-machine verwenden will, wird es sehr Komplex (beispiel siehe Anhang). Ich mache meine Parser stattdessen gerne so, dass ich bei jedem Durchlauf immer maximal ein Zeichen einlese, und speichere dann einfach alle nötigen Zustände.
Anbei mal mit strchr + atoi. Das komplette Programm belegt 296 Byte auf dem ATtiny85.
1 | bool get_vals(char* buf, uint8_t* vals, uint8_t count) |
2 | {
|
3 | buf = strchr(buf, '*'); |
4 | for (; count; count--) |
5 | {
|
6 | if (buf == NULL) |
7 | return false; // no success |
8 | *vals++ = atoi(++buf); // read number after delimiter |
9 | buf = strchr(buf, ','); // point to next delimiter |
10 | }
|
11 | return true; // wanted count of numbers read |
12 | }
|
Ups, da war noch ein Fehler drin, jetzt sollte es stimmen. [Mod: altes Attachment gelöscht]
:
Bearbeitet durch Moderator
Mal das Ausgangsbeispiel (*127,255,001,111#) in EBNF/Wirth-Notation übertragen mit der Annahme, dass immer alle vier Werte vorhanden sein müssen: cmd_line := start unsigned comma unsigned comma unsigned comma unsigned end unsigned := digit { digit } digit := '0' .. '9' start := '*' end := '#' comma := ',' Zur Notation: irgendwas = muss (an der Stelle) vorhanden sein [ irgendwas ] = kann 0 oder 1 mal vorhanden sein { irgendwas } = kann 0 oder n mal vorhanden sein 'irgendwas' = Terminal, muss dort so vorhanden sein Damit lässt sich dann ein simpler Scanner/"Parser" basteln (auch wenn der hier mMn etwas Overkill ist):
1 | // Nur als grober Ansatz
|
2 | // Nicht alle möglichen Fehler werden abgefangen
|
3 | // Noch wurde das ausgiebig getestet
|
4 | enum TokenType { START, END, COMMA, UNSIGNED } ; |
5 | typedef struct { |
6 | TokenType Type; |
7 | unsigned int Value; |
8 | } Token; |
9 | |
10 | static bool parse(const Token* tokens, size_t num_tokens) { |
11 | if (!tokens) return false; |
12 | if (tokens[0].Type != START) return false; |
13 | if (tokens[1].Type != UNSIGNED) return false; |
14 | if (tokens[2].Type != COMMA) return false; |
15 | if (tokens[3].Type != UNSIGNED) return false; |
16 | if (tokens[4].Type != COMMA) return false; |
17 | if (tokens[5].Type != UNSIGNED) return false; |
18 | if (tokens[6].Type != COMMA) return false; |
19 | if (tokens[7].Type != UNSIGNED) return false; |
20 | if (tokens[8].Type != END) return false; |
21 | return true; |
22 | }
|
23 | |
24 | static bool scan(const char* input, size_t input_len, Token* tokens, size_t max_num_tokens) { |
25 | size_t index = 0; |
26 | size_t num_tokens = 0; |
27 | if (!input || !tokens) return false; |
28 | while (index < input_len) { |
29 | if (num_tokens >= max_num_tokens) break; |
30 | if (iswspace(input[index])) { |
31 | index++; |
32 | continue; |
33 | }
|
34 | if (input[index] == '*') { |
35 | tokens[num_tokens].Type = START; |
36 | tokens[num_tokens].Value = 0; |
37 | num_tokens++; |
38 | index++; |
39 | continue; |
40 | }
|
41 | if (input[index] == '#') { |
42 | tokens[num_tokens].Type = END; |
43 | tokens[num_tokens].Value = 0; |
44 | num_tokens++; |
45 | index++; |
46 | continue; |
47 | }
|
48 | |
49 | if (input[index] == ',') { |
50 | tokens[num_tokens].Type = COMMA; |
51 | tokens[num_tokens].Value = 0; |
52 | num_tokens++; |
53 | index++; |
54 | continue; |
55 | }
|
56 | |
57 | if (isdigit(input[index])) { |
58 | unsigned int value = 0; |
59 | // insert overflow checks...
|
60 | while (index < input_len) { |
61 | if (!isdigit(input[index])) break; |
62 | value *= 10; |
63 | value += (input[index] - '0'); |
64 | index++; |
65 | }
|
66 | tokens[num_tokens].Type = UNSIGNED; |
67 | tokens[num_tokens].Value = value; |
68 | num_tokens++; |
69 | continue; |
70 | }
|
71 | // anything else = error
|
72 | return false; |
73 | }
|
74 | return true; |
75 | }
|
76 | |
77 | // Aufruf dann bspw.:
|
78 | char* instr = "*127,255,001,111#"; |
79 | Token tokens[20]; |
80 | bool scan_res = scan(instr, strlen(instr), tokens, 20); |
81 | bool parse_res = parse(tokens, 20); |
Für solche Aufgaben sind regex doch wie geschaffen. Auf AVR habe ich die bisher nicht benutzt, sind die in der avr-libc implementiert? Eine kleine Implementation (2 KB Code) liegt auf Github rum: https://github.com/kokke/tiny-regex-c
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.