Hallo, ich stelle mal hier die neueste Variante meines Parsers vor. Er ist recht "kompakt" (zw 1.5 und 1.9kB) und bietet dabei eine Menge. Das Demo erfordert einen ATMega32 (hatte ich gerade so herumliegen, ein Mega8 tuts auch) mit Pegelkonverter für RS232, sowie einen Baudratenquarz. In der readme.txt steht alles notwendige drin, und habe mal das wichtigste hier reinkopiert : Was kann der Parser ? --------------------- Der Parser kann Parameter wie Zahlen, Namen (16 Zeichen), Strings (16 Zeichen), und durch Erweiterung wie in diesem Demo gezeigt Boolean (on,off,true,false...) und Datumsangaben (einfaches dd.mm.yy Format, ohne Prüfungen) verarbeiten. Diese Parameter werden Codecs genannt. Die Codecs für Zahlen, Namen und Strings) sind fest eingebaut, alle weiteren sind optional. Die Befehle die der Parser erkennen kann sind in uParse_cfg.h definiert. Beispiel : UPARSE_ITEM(SETVAL, "set \x02 = \x01", FUNC_EG_SET) UPARSE_ITEM(name , text , funktionsidx) Es wird ein Eintrag SETVAL generiert (enum und datenmäßig) bestehend aus dem Text und einem Funktionsidx. (siehe Abschnitt UPARSE_FUNCTION). Die Steuerzeichen \x02 und \x01 im Text rufen den gewünschten Codec auf. \x01 bedeutet Zahl \x02 Name und \x03 String. Alles über \x03 sind ggf. definierte User-Codecs. Gibt der Benutzer nun "set name = 123" ein so wird die Funktion die sich hinter FUNC_EG_SET verbirgt (in dem Falle "vCmdSet") mit dem enum für SETVAL und einem Parameterarray auf. Dieses Array besteht aus 4 (siehe PARSE_DATA_ELEMENTS) Elementen. Jedes Element umfasst ein Union aus einem 32 Bit unsigned long und einem (siehe PARSE_DATA_STR_LEN) 16-Byte Array (für Namen usw), sowie ggf. User-Definierte Daten für die User-Codecs. Zusätzlich gibt es einem Byte welches besagt um welchen Datentyp es sich handelt. In diesem Falle wäre das stCmdDatas [0].ucType = 2 stCmdDatas [0].aucName = "name" und stCmdDatas [1].ucType = 1 stCmdDatas [1].ulLong = 123 Mit diesen Parametern und dem enum für SETVAL (hier 2) wird vCmdSet aufgerufen. Der Parser ist sehr flexibel was die Eingabe angeht. So werden Zahlen wie 0x2345, -0b110010, $1234, -5, 3827362 erkannt. Auch bei den Spaces ist der Parser nicht so restriktiv. Wenn in der "Vorlage" ein Space steht können beliebig (aber auch keins !) viele Spaces folgen bevor die Verarbeitung das nächste Zeichen abarbeitet. Tabs werden zu Spaces. Eine Umschaltung zwischen case sensitive/case insensitive ist mit dem Schalter PARSE_IGNORE_CASE in der Konfiguration im Abschnitt PARSE_CONFIG möglich. So würde " sET nAmE =123 " denselben Aufruf zur Folge haben. Weiterhin arbeitet der Parser so lange bis das EOT (\0) erkannt wurde oder ein Fehler vorliegt. Z.B. "cmd test test = 0x345 backlight off" würde jeweils 1 mal FUNC_EG_CTRL, FUNC_EG_SET und FUNC_EG_TEST mit den entsprechenden Parametern aufgerufen werden. Im Fehlerfalle kann die Position des Fehlers zurückgegeben werden. Es lässt sich weiterhin eine automatisch generierte Hilfe nutzen. Diese listet bei Eingabe von "help" alle bekannten Befehle auf und entschlüsselt die Steuerzeichen für die Codecs in Klartext. User-Definierte Codecs weden (soweit benannt) ebenfalls aufgelistet. --- Zitat-Ende --- Verwendung findet solch ein Parser überall da wo man Funktionsteile testen, Parameter setzen, Operationen antriggern muß. In Verbindung mit einem UART ergibt sich sehr leicht eine Kommando-Shell die es erlauben bequem mit dem System zu arbeiten. In Verbindung mit einem Speichermedium und einem Dateisystem wäre Scripting (vllt auch ein Interpreter) möglich. Dieser Parser ist keineswegs auf den AVR beschränkt (selbst wenn er "danach" programmiert worden ist; es genügt eine handvoll Makros und ess lässt sich auf (fast) jede Plattform portieren). Viel Spaß damit ...
Da diese Software anscheinend fehlerfrei ist (und um den Thread nochmal nach oben zu holen) :-))) kündige ich meine nächste Untat an. Es wird (nach dem selben Prinzip, nur etwas anders aufgebaut) einen XML-Reader geben. Dieser wird die XML-Datei Zeichen für Zeichen analysieren und bei den jeweiligen Knoten (und noch in anderen Situationen) eine Benutzer-Funktion aufrufen in der die Daten dann weiter verarbeitet werden können. Das ganze soll ebenfalls so kompakt wie möglich sein aber gleichzeitig eine gewisse Flexibilität geben. Woher die XML-Daten kommen ist zweitrangig, da der Anwender ein paar kleine Funktionen zu Verfügung stellen muß, damit der Parser arbeiten kann. Dadurch wird es obsolet ob die Daten im EEProm, im Flash, auf einer SD-Karte oder sonstwo stehen. Mögliche Anwendungen wären : - einfache Konfiguration der Applikation - Import von Strukturierten Daten - Verarbeiten von Playlists/Anweisungslisten - alles was mir gerad nicht einfällt ...
Was macht man mit so einem Parser? Schilder doch bitte mal einen Anwendungsfall aus der Praxis-
Was man mit dem Parser machen kann habe ich ja schon beschrieben : - Debugshell - Monitorprogramm - Steuern In Verbindung mit einem PC Programm muß man dann nur noch die Befehle rüberschicken, und man kann beim Sniffen direkt im Klartext lesen was passiert. Bei Binärdaten ist das nicht ganz so einfach, da man ja immer ein spezielles Programm benötigt, welches das Protokoll unterstützt. So reicht ein einfaches Terminalprogramm um alles steuern zu können, und man hat nicht das Problem das wenn sich etwas an den Funktionen ändert jedesmal das PC-Programm ändern muß bzw dann passt die Version nicht mit der SW im uC zusammen usw .... Mit einem Terminal kann man alles direkt erreichen. In Verbindung mit einer SD-Karte : - Steuerskripte - Interpretersprache Auf größeren Systemen mit mehr Speicher (teste ich gerade) - Ansätze für einen Compiler Ob das praxisauglich ist weiß ich nicht. Aber zum Debuggen ist es sehr hilfreich. Und um sich beispielsweise ein Single-Chip-Computer-System aufzubauen sicherlich auch. Ob man sowas in professionellen Geräten benötigt weiß ich nicht. Aber eine Debugshell kann beim Entwickeln immer hilfreich sein denke ich. Mein spzieller Anwendungsfall ist mein Audio-Projekt. Mit dem Parser kann ich da folgendes machen : - Einstellen des Codecs - Auslesen/Beschreiben des EEProms/DataFlashs - Berechnen der Tabellen und speichern im Datenflash - Setzen/Auslesen der Werte für die Audio/Steuermodule - Ändern der konfiguration/Verbindungen der Audio/Steuermodule (dadurch wird das ganze erst zum Modular-System) - Disassemblieren und Assemblieren des Programms für den DSP - Auslesen der Speicherbereiche für den DSP.
Hallo Rene, das Thema interessiert mich brennend. Aber ich habe keine Ahnung von Parsern, eventuell solltest Du "umgangssprachlich" ein wenig helfen: Also, meine Anwendung sieh so aus: Ich sende über Funk von einem PC Befehle und Parameter an einen MC. Der wiederum steuert ein Gefährt an. Es soll eine Rückmeldung (Info, Success, Warning, Fatal, ... etc.) für den ausgeführten Befehl zurückgegeben werden. Verstehe ich es richtig, dass ich für jeden Befehl, bzw. Typ den ich interpretieren will, eine Routine in einem Definitions-File angebe, die dann nach der Befehlserkennung angesprungen wird und den angegeben Parameter verarbeitet ? Wobei Befehle aus einem einzelnen Buchstaben oder mehreren Buschstaben bestehen können ? Migration auf einen Mega 256x sollte ohne weiteres lediglich durch UART-Anpassung gehen ? LG
@John >Verstehe ich es richtig, dass ich für jeden Befehl, bzw. Typ den ich >interpretieren will, eine Routine in einem Definitions-File angebe Kann man so machen. Man kann aber auch eine Routine für alle Befehle oder Befehle die zusammengehören. (z.b. Parameter setzen, Gefährt bewegen, Debug-Aufrufe, Statusabfrage). Man bekommt ja die Befehlsnummer (zu der es einen enum gibt) und kann/muß diese dann per switch/case Methode entsprechend "auswerten" (sprich den gewünschten Befehl ausführen). Wenn du mit Typ einen Datentyp meinst der keine Zahl/String/Name ist, sondern beispielsweise ein bool, ein Datum, eine IP-Adresse, oder sonstwie geartete Daten die von Text -> Binär umgesetzt werden müssen, musst du einen Codec schreiben, der die "Eingabe" (man wird dann vom Parser aufgerufen) prüft und in Binärdaten umsetzt. Dieser Codec lässt sich für jeden x-beliebigen Befehl (der in UPARSE_ITEM definiert ist) einsetzen. >Wobei Befehle aus einem einzelnen Buchstaben oder mehreren Buschstaben >bestehen können ? Richtig. Die Eingabe wird Zeichen für Zeichen mit der Befehlsliste verglichen und bei den Steuerzeichen in der Befehlsliste wird die Eingabe auf den jeweiligen Datentyp geprüft. Ob der Befehl aus einem Zeichen, oder einem halben Roman besteht ist letztenendes egal. >Migration auf einen Mega 256x sollte ohne weiteres lediglich durch >UART-Anpassung gehen ? Der Parser selbst hat keine UART Unterstützung. Dem Parser ist es letztenendes egal wo der Input herkommt. Das kann ein UART, ein Flash, ein EEProm, oder eine SD-Karte sein. Der Parser selbst benötigt nur einen Zeiger auf den zu bearbeitenden Eingabestring im RAM. Also bei einem UART würde dieser Zeichen für Zeichen entgegennehmen und alles bis auf \x0a oder \x0d wegspeichern. Bei einem \x0a oder \x0d würde der Eingabestring mit \0 abeschlossen und der Parser mit diesem String aufgerufen werden (natürlich nicht aus dem Interrupt heraus :-)). Bei einer SD-Karte würde man ähnlich verfahren. Zeile bis zum CRLF einlesen und im RAM speichern und bei CRLF den Parser dann aufrufen (Eingabestring vorher abschließen). Dadurch das der Parser keine IO Routinen anspricht/benutzt kann dieser auf jedem System (egal ob AVR, ARM, PC oder sonstwas) eingesetzt werden. Er dient also lediglich der Umsetzung zw. Klartext-Befehlen und Aufruf mit den übergebenen Parametern in Binärform. Beispiel : uParse_cfg.h : UPARSE_ITEM(MOVE_FWD, "move ffw", FUNC_MOVE) UPARSE_ITEM(MOVE_BWD, "move back", FUNC_MOVE) UPARSE_ITEM(MOVE_LFT, "move left", FUNC_MOVE) UPARSE_ITEM(MOVE_RGT, ´ "move right", FUNC_MOVE) UPARSE_ITEM(MOVE_DIR_STP, "move \x01, \x01", FUNC_MOVE) UPARSE_ITEM(MOVE_DIR, "move \x01", FUNC_MOVE) Der Parser versteht danach 6 Befehle (mehr). - move ffw - move back - move left - move right - move zahl, zahl - move zahl bei Erkennen einer dieser Befehle wird die Funktion die hinter "FUNC_MOVE" steht mit dem entsprechenden enum (MOVE_FWD, MOVE_BWD, MOVE_LFT, MOVE_RGT, MOVE_DIR_STP, MOVE_DIR) und den Parametern aufgerufen. Beispiele : move 3, 30 -> enum = MOVE_DIR_STP, stCmdDatas [0].ulLong = 3, stCmdDatas [1].ulLong = 30 move fwd -> enum = MOVE_FWD Es bleibt dem Benutzer überlassen ob er alle Befehle in einer Aufrufer-Funktion (wie z.b. FUNC_MOVE) oder über mehrere Funktionen verteilt. Die Erweiterung um neue Befehle erfolgt derart einfach das es im Prinzip nicht mehr einfacher geht. Man muß nur einen Text-Befehl in der uParse_cfg.h hinzufügen und (im Falle einer schon vorhandenen Aufrufer-Funktion mit switch/case) den case für den neuen Enum hinzufügen. uParse_cfg.h ------------ UPARSE_ITEM(SET_PORT_A, "set port a = \x01", PORT_FUNC) Ausführendes C-Modul, Funktion die hinter PORT_FUNC steht --------------------------------------------------------- case SET_PORT_A : PORTA = stCmdDatas [0].ulLong; break; Fertig ! Hoffe das Prinzip ist klar geworden. Dem Parser ist es egal wo der Eingabestring herkommt und was die Funktion die diesen Befehl ausführen soll damit macht. Der Parser geht nur Zeile für Zeile in seiner Liste durch, und wenn er einen Befehl gefunden hat der zu der Eingabe passt wird die Funktion die hinter dem Befehl steht aufgerufen mit dem Enum des Befehls und den umgewandelten Parametern. Man kann das wie schon erwähnt für Steuerzwecke (Steuerung über einen PC mti Terminal-Programm, einem PC Programm welches Befehle im Klartext über die Leitung schickt, eine SD-Karte mit einem Script, oder oder oder ...), oder Einstellen von Parametern oder eben zu Debugzwecken (es ist einfach nur praktisch eine Funktion testen zu können indem man einen bestimmten Befehl eingibt, und diese Funktion dann mit evtl. übergebenen Parametern aufgerufen wird). Hoffe ich konnte etwas Klarheit in die Sache bringen.
Hallo Rene, danke erst einmal: Das ist genau, wonach ich schon lange gesucht habe. Brauche etwas Zeit zur Implementierung, werde mich aber in jedem Falle hier wieder melden. Grüße
@john Freut mich das du den Parser brauchen kannst. Die Implementierung dürfte recht einfach sein. Für das Interfacing musst du dir im Prinzip nur die main.c anschauen. Dann sollte alles klar werden. Wenn nicht, frag einfach. Du kannst ja vorstellen was du gemacht hast, und deine Erfahrungen dazu posten. Bin für Feedback immer dankbar.
Hallo Rene, bevor ich anfang, mich durch deinen gesamten Code zu wühlen, frag ich lieber kurz hier, ob dir spontan zu folgendem Problem was einfällt: Ich hab deinen Parser mit dem Beispielprogramm für einen atmega644 übersetzt. Ausser einigen Register-Namen für die UART und den Namen der ISR hab ich nichts verändert. Die Startmeldung erscheint, ich kann etwas eingeben, aber sobald ich Enter drück ist das ding bis zum nächsten Reset tot. Hab ich was übersehen? Harry
@Harald Wenn du mit AVR-Studio arbeitest, mußt du in den Projekt-Optionen "Unsigned Chars" wegclicken. Hatte ich vergessen zu erwähnen. Asche auf mei Haupt. Ich verwende signed chars (ist bei Abfragen einfacher). Bei unsigned chars hängt das Dingen nur. Muß das nochmal beheben, sodas es unabhängig davon ist (also casting noch vervollständigen). Ein weiteres kleines Problem (habe ich schon behoben, aber noch nicht hochgeladen) ist das 0 als Zahlenwert nicht erkannt wird. 0x0 sollte hingegen schon erkannt werden. Werde die korrigierte Version hier noch hochladen.
Hallo Rene, ich arbeite nicht mit AVR-Studio, sondern mit Eclips. Da der Parser in ein bereits vorhandenes Projejt intgriert werden soll, kann ich auf unsigned chars nicht verzichten (genauso wenig wie auf signed) So ist das leider keine sehr saubere Lösung! Ein Hinweis zum Schluss: achte bitte bei der Benennung von Dateien auch auf Gross- Klein-Schreibung. In dem von dir bereitgestellten Code wird die include-Datei nicht gefunden, da das P im Dateinamen gross geschrieben wurde, dies aber im übrigen Quelltext nicht berücksichtigt wurde. Gruss Harry
@Harald Na ja. Wenn man nur mit AVR Studio und Visual Studio arbeitet hat man vllt nicht die Ambitionen es noch auf Eclipse zu Testen. Da das ganze auf der Windows-Plattform Entwickelt worden ist hab ich mich bei Dateinamen erstmal nicht um Groß/Kleinschreibung gekümmert. Aber die Sache mit den unsigned/signed chars werde ich noch ändern. Da ich selbst öfter mal drüber stolpere denke ich muß ich da wohl mal aufräumen. Ich mache mal eine neue Version fertig und werde diese Hochladen.
Hier das angekündigte Update. Sry an all diejenigen die den Parser versucht haben zu nutzen und dabei Startschwierigkeiten hatten. Es ist nun behoben : - signed/unsigned-"Problematik" - Problem mit der Eingabe von 0, 1 als Zahl (0x0 oder $0 wären gegangen) - soweit ich Testen konnte Groß/Kleinschreibung in Dateinamen für die Linux-Leute Für weiteres Feedback wäre ich dankbar.
Du hattest ja schon mal sowas gemacht. Damals war es leider schon so, dass man nicht unbedingt die richtige Anwendung dafür hat. Ich überlege gerade, wo ich das mal einsetzen könnte. Irgendwie ne Kommandozeile über eine serielle Schnittstelle wär mal was... Hm. Wenn ich sowas jedenfalls mal brauche und ich keine Lust habe sowas selber zu machen (Das werde ich sicher nicht haben), dann weiß ich ja wo ich sowas finden kann! ;-)
@Simon ich geb zu das es kein Fading-LED, keine TCP/IP-Implementation, keine FAT16/32 ist, aber wie du schon sagtest : Kommandozeile für den uC via serielle Schnittstelle (oder wenn man einen VGA/BAS-Teil hat auch direkt am Fernsehen) ist eine Möglichkeit. Ich finde es einfach nur bequem wenn ich selbstgeschriebene Funktionen "mal eben" schnell testen kann. Oder wenn ich ein System habe das dynamisch Code von SD-Karte nachladen kann könnte ich sogar eine gewisse DOS-Funktionalität nachbilden. Oder das Grundgerüst für einen BASIC (oder sonstwas) Interpreter.
Für Haussteuerung: "Tür auf" "Licht WZ AN" (Wohnzimmer) "Toaster EIN" "Brotintoasterwerfer Scheibe 1" (1 Stück) "Fenster zu" "Rolladen hoch" "Alles AUS" usw.
Hallo Rene, lach - nachdem du meinem Thread deine Werbung aufgedrückt hast... revanchiere ich mich ;O) Hallo Leute - hier ist noch ein Parser - find das ulkig vor Wochen hab ich gar nichts gefunden und auf einmal... Beitrag "Re: Einfacher Interpreter für Komandozeilen/Befehlszeilen" Aber wenn ich das sehe verfolgen wir beide etwas unterschiedliche Konzepte. Was mir bei einem gut gefällt ist die Möglichkei mit den "Token" mit denen ich ein Kommando wie "set" noch parameterieren kann ohne zusätzlich Code zu erzeugen. Dafür erscheint mir meiner erst einmal etwas übersichtlicher, aber das liegt im Auge des Betrachters und da bin ich eindeutig befangen ;O) Ich werde die Anregung mal mitnehmen. Vielleicht mergen wir die Threads dann ;) Good coding, Gary
Hallo Gary, das ist nur gerecht :-))) Vllt hätte ich meinen Parser "Erweiterter einfacher Parser für Kommandozeilen" nennen sollen gg. Aber die unterschiedlichen Konzepte sind schon sichtbar, wobei ich es bei meinem Parser vorteilhaft finde das dort Parameter direkt als Binärwerte umgesetzt werden (Zahlen, oder wie im Beispiel Bool, Datum, oder sonstiges) können und quasi direkt "mundgerecht" serviert werden :-) und die Zubereitung dieser Werte kann sogar durch eigene Zutaten erweitert werden, um mal kulinarisch zu bleiben :-)
Hallo Rene, zuerst einmal Danke für Deinen inspirierenden Code zum Thema kompakter Parser. Ich selbst benötige etwas sehr Ähnliches zum Bedienen eines GSM Modems (parsing of AT command response). Zum Einen habe ich einige zusätzliche Anforderungen (andere features benötige ich wieder nicht), zum Anderen handelt es sich bei dem Projekt leider um ein closed source Projekt. D.h. ich bin genötigt einen eigenen Parser zu schreiben. Soweit zu meiner Geschichte. Ich habe jetzt noch eine Frage zur Genese Deines Projektes. Bei der ersten Variante (uShell) hast Du noch auf eine Trennung von Tokenizer und Parser gesetzt (analog zu einem Konzept wie flex, bison) - bei dieser neuen Variante setzt Du nun auf ein zeichenweises parsen. (bitte korrigiere mich falls ich falsch liege). Warum hast Du das umgestellt? Was waren die Nachteile des Token basierten Konzeptes? Vom Gefühl her hätte ich gesagt, dass die Klassifizierung in Tokens effektiver ist als die Einordnung der Zeichen in Klassen wie Du es jetzt machst. Ich würde mich über Rückmeldung freuen. Grüße, Michael
Das mit der Trennung in Tokenizer und Parser bei der uShell hast du richtig erkannt :-) Ich hatte das damals gemacht weil ich versucht habe ohne "Eingangspuffer" auszukommen. Ich habe allerdings lediglich den Eingangspuffer für die Tokens eingespart. Die erkannten Tokens landen dann aber dennoch in einem Puffer. Das eigentlich besondere an der uShell ist eben die Tokenerkennung. Es wird zu jeden Token ein Bit gesetzt ob das aktuelle Zeichen noch in übereinstimmung mit dem Token steht. Wenn nicht, wird dieses Token auch nicht weiter untersucht. Letztenendes habe ich umgestellt da mir die Erstellung der "Sätze" die der Interpreter verstteht einfach zu umständlich war. Erst Tokens definieren, dabei aufpassen das diese nicht schon existieren. Dann das umständliche Zusammenbauen der Tokens zu einem Satz. Irgendwie war mir das alles zu lästig. Außerdem hat es mehr Platz benötigt als der neue Interpreter. Und da man die zeichenweise Verarbeitung ja nicht unbedingt im Interrupt machen sollte, kommt man um einen Eingangspuffer ohnehin nicht herum. Der neue Parser ist zwar langsamer dadurch das er eben alle Sätze immer durchsucht, aber dafür ist dieser kompakter und für die meisten (meiner Anwendungen) ausreichend. Die Einteilung in Klassen kam eigentlich daher das ich versucht habe einen kompakten XML-Parser zu programmieren. Dieser besteht wie der neue Parser auch aus 2 Tabellen. Eine für die Zeichenklasse, die andere als Statemachine. Das war der Ausgangspunkt meinen Parser zu überarbeiten. Den ursprünglichen XML-Parser hab ich noch nicht eingesetzt, ist aber für die Möglichkeiten die er bietet eben sehr kompakt. Evtl stelle ich den hier auch nochmal vor, wenn ich denn mal ein AVR-Demo dazu fertig habe :-) Freut mich aber das sich jemand mal mit meinen Interpretern auseinandersetzt bzw durch meinen Code inspiriert worden ist :-) Falls ich dir bei deinem Problem helfen kann bzw noch weitere Inspiration geben kann, gerne ... schreib mir einfach ne PN.
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.