Hallo, ich stelle euch mal meinen universellen Parser für uCs vor. Der Parser ist in C geschrieben und sollte sich problemlos auf allen uC's implementieren lassen (einzig bei den AVRs müsste man noch was machen, damit die Tabellen im Flash liegen, sonst wird aus dem knappen RAM gelesen). Merkmale des Parsers : - optimiert für RAM Verbrauch (es wird kein Eingabe Buffer benötigt, alle anderen Buffer sind konfigurierbar) - relativ schnell (es wird kein strcmp verwendet) - Value-Codecs. Das sind externe Funktionen mit denen aus dem eingehenden Text Binärdaten zusammengebaut werden können (z.b. Hex Codec, String Codec oder auch selbstdefinierte strukturen) Die Konfiguration des Parsers wird über ein Konfigurationsfile (parser_cfg.h) zur Compilierzeit eingestellt. In diesem File können : - Schlüsselwörter definiert werden - Grundlegende Zeichen (Ende-Zeichen,Reset Zeichen) gesetzt werden - die einzelnen Befehle (Parse-Lines) - die Value-Codec Funktionen angegeben werden Das Demo selbst ist in VC++ geschrieben, der Parser Kern liegt in einem eigenen Ordner. Der Parser exisitiert schon seit knapp 2 Jahren und läuft (wenn er richtig konfiguriert ist) einwandfrei auf einem MSP430 und einem R8C. Der Parser hat nur 2 kleine einschränkungen : - Bei der definition der schlüsselwörter muß darauf geachtet werden das jedes schlüsselwort eindeutig von anderen zu unterscheiden ist (bsp. "VER" und "VERSION" geht nicht, da VER in VERSION enthalten ist. - Die Value-Codecs können NUR durch ein einleitendes Schlüsselwort aufgerufen werden (bsp. 0x..., $.... oder "...", nicht aber 100H, 64L) Viel Spaß beim proggen und testen Gruß Rene
TheMason wrote: > Der Parser hat nur 2 kleine einschränkungen : > - Bei der definition der schlüsselwörter muß darauf geachtet werden das > jedes schlüsselwort eindeutig von anderen zu unterscheiden ist (bsp. > "VER" und "VERSION" geht nicht, da VER in VERSION enthalten ist. Du meinst Präfixfrei? Weil VER und VERSION kann man shcon eindeutig unterscheiden ;)
machbar ists bestimmt. vielleicht in einer späteren version. erstmal schauen wer diese version gebrauchen kann :-) gruß rene
geht leider nicht :-( man kann sich aber behelfen. man kann "v" und "ersion" in 2 tokens aufspalten und bei den parse-lines einfach ein token mit V und eins mit ERSION eintragen. (dasselbe ginge sicherlich auch mit "VER" und "SION") dann muß allerdings das zusammengesetzte "token" (also die parse-line) vor den "kurzen" parse-lines . also bsp weise : PARSE_BEGIN (PARSER_VERSION, vVersion) PARSE_DEF (TK_V) // version über token "V" ... PARSE_DEF (TK_ERSION) // ... und "ERSION" PARSE_END PARSE_BEGIN (PARSER_V, vVersion) PARSE_DEF (TK_V) // nur "V" PARSE_END da ist sicherlich noch handlungsbedarf und bugfixing :-)
hat das jemand mal getestet und kann feedback geben ? gruß rene
TheMason wrote:
> kein feedback ?! *grummel
Das fehlende Feedback liegt vielleicht daran, daß keiner weiß, was er
damit auf nem MC machen soll.
Schreib dochmal, wozu man sowas praktisch anwenden kann (was Du z.B.
damit machst).
Peter
>Schreib dochmal, wozu man sowas praktisch anwenden kann (was Du z.B. >damit machst). Ist glaube ich ne gute idee :-) Was kann man damit machen ? Mit der uShell (bzw. dem Parser) lässt sich (wenn man z.b. eine RS232-Schnittstelle hat) sehr leicht eine Kommandozeile implementieren. Ich benutze das z.b. zum Testen von Funktionen bzw. Programmteilen. Es lässt sich damit auch eine richtige Mini-Shell realisieren. Oder wenn man Steuerungen hat lassen sich diese einfach per Konsole parametrieren bzw. einstellen. Wo habe ich das verwendet ? Ich habe in verbindung mit einem vga-text-interface auf einem fpga eine konsole (daher der name uShell) gemacht damit ich meine mmc routinen einfach testen konnte. Mit Befehlen wie dir, cd file, read sector konnte ich mir später dann die MMC Karte anschauen. Bei dem Audio-Projekt habe während der Tests damit den DAP gesteuert. Z.b. den Programm und Koeffizienten Speicher schreiben und lesen (Binärdaten sind so schwierig einzugeben :-)). Später habe ich dann einen Synthesizer programmiert den ich über die uShell gesteuert habe. (filter, hüllkurve, sequenzer) Erst habe ich mit einem Terminal-Programm gearbeitet, und nachdem alles soweit funktionierte hab ich ein Delphi-Programm geschrieben mit dem ich alles grafisch machen konnte, der aber auch nur die befehle direkt als text sendet, sodass ich immer noch per terminal auf den dap zugreifen kann und nicht noch extra einen binär-modus einbauen muß. Ressourcenverbrauch ? Das ganze ist recht kompakt und ressourcensparend geschrieben. Man braucht wenig RAM, es wird pro Token (also Schlüsselwort oder Symbol) ein Bit benötigt, einen Buffer für die Tokens und einen Buffer für Parameter (Values). (beide Buffer sind konfigurierbar) Man braucht wenig Flash. Das Programm selbst besteht aus 4 großen funktionen und mehreren (ca 10) kleineren Funktionen. Auf einem ARM braucht das ganze 2.5K (ohne optimierung). Auf einem MSP430 brauche ich etwa 1.5K (ohne optimierung). Die uShell ist also nichts für kleine und kleinst uCs. Aber ab 8-16k lohnt es sich. Beim AVR ists etwas kritischer, da man unter C nicht transparent auf das Flash zugreifen kann. Wenn man die uShell jedoch für AVR anpasst dürfte es dort auch recht wenig Platz brauchen. Das was den Flashverbrauch bestimmt sind die Tokens, die Parse-lines (aufbau der bekannten befehle) und die value-codecs (s.u.) welche zusätzlichen code benötigen. Funktionsweise Es gibt im Parser keinen (!) Text-Buffer, sondern es wird mit jedem Zeichen sukzessiv dasjenige Token gesucht/"herausgefiltert" welches passt/passen könnte. Dabei wird jedem Token ein bit zugeordnet (welches zu anfang 1 ist). Auf jedes ankommende Zeichen das auf ein/mehrere tokens passt bleibt das bit 1, passt es nicht ist es 0. es werden mit jedem durchlauf die anzahl möglicher passender tokens gezählt und wenn nur noch eines übrig ist und die länge passt ist es das wohl. Dieses Token kommt in einen Token-Buffer. Diese Größe dieses Buffers bestimmt die länge maximale des befehls (in tokens). Um Parameter zu übergeben kann man spezielle tokens definieren, die, sobald sie erkannt wurden, die kontrolle über den zeichenstrom an eine benutzerdefinierte funktion übergeben, die den zeichenstrom zu binärdaten konvertiert (z.b. Hexadezimal "0x..." oder Binär "0xb...") und (sofern richtig eingegeben :-)) im value-buffer speichert. wird nun ein ende-zeichen (von mir aus CR) erkannt wird der tokenbuffer mit den parse-lines verglichen und sobald eine passende gefunden wurde, wird die dahinterstehende funktion ausgeführt. die aufgerufene Funktion bekommt noch mitgeteilt welche parse-line sie aufgerufen hat, und einen zeiger auf den value-buffer und kann dann ihre Funktion mit den entsprechenden daten (wenn vorhanden) ausühren. Hoffe ich konnte den genaueren Nutzen und die Funktion dahinter etwas klären, und hoffe auf Feedback (ohne *grummel :-)) Gruß Rene
Ich muß leider zugeben, daß ich da überhaupt nicht durchsehe. Dazu trägt auch das überbreite Format bei, das ich selbst als Vollbild nicht ohne Zeilenumbrüche ansehen kann. Zeitungen drucken nicht deshalb in Spalten, weil sie zu doof sind, breit zu drucken, sondern weil es wesentlich besser lesbar ist. Ich bin da nicht so der SW-Profi, der mit den vielen #ifdef, #undef und tief geschachtelten Macros zurechtkommt. Ich mache solche Sachen lieber einfacher und direkter. Ist dann vielleicht nicht so super universell, aber ich blicke durch. Anbei mal ein Beispiel für meinen Commandointerpreter. Er arbeitet ganz einfach mit ner Liste (String, Funktion), die abgeklappert wird. Groß-,Kleinschreibung wird ignoriert. Wenn man die längeren Strings zuerst hinschreibt, sind auch Teilstrings als andere Kommandos zulässig. Alle Zeichen nach dem Ende des Strings sind optinal. Es läuft auf nem 8051, das "code" bedeutet dort, daß die (konstante) Variable im Flash landet. Peter
also sw-profi bin ich sicherlich auch nicht, aber das arbeiten mit dem präprozessor erleichtert einem einiges (speziell wenns darum geht tabellen zu generieren, was bei der uShell gut genutzt wird). falls interesse besteht kann ich das ganze ja mal "unrollen" um zu zeigen wie der parser funktioniert, und was der präprozessor genau macht.
TheMason wrote: > falls interesse besteht kann ich das ganze ja mal "unrollen" um zu > zeigen wie der parser funktioniert, und was der präprozessor genau > macht. Das wäre Klasse. Interesse, wie das funktioniert hätte ich gerne schon :-)
komme leider erst heute dazu den code mal "unzurollen". kleiner hinweis noch zum thema tabs. ich verwende tabs mit 2 spaces. ich werde das in mehreren teilen machen. zuerst gehe ich auf die nutzung des präprozessors ein. da dies der eigentlich "knifflige" teil ist muß hier wohl etwas mehr zu geschrieben werden. das zentrale element des parsers ist die konfigurationsdatei "parser_cfg.h" diese wird mehrmals im code, sowie im header "geladen" (includiert). diese konfigurationsdatei enthält mehrere abschnitte. mit diesen abschnitten werden tabellen (zeigertabellen oder arrays von strukturen) und andere daten definiert. aber auch externals, enums oder einfach nur konstanten (per makro-define) werden definiert. es wird bei jedem laden ein anderer teil des konfig-files "freigeschaltet" bzw. eine andere interpretation der dort angelegten tabellen und daten vorgenommen. das freischalten bzw. mehrfachnutzen von konfigutationselementen wird innerhalb des konfigurationsfile ausschließlich über #ifdef KONFIGURATIONS_ELEMENT ... ... Tabellen und/oder Daten ggfs KONFIGURATIONS_ELEMENT(...) ... #endif ermöglicht. im sourcecode werden diese abschnitte ausschließlich über #define KONFIGURATIONS_ELEMENT // oder auch mit () und parametern ... ... #undef KONFIGURATIONS_ELEMENT eingeblendet oder verarbeitet. das #undef ... ist dabei wichtig um mehrfachdeklarationen zu erlauben. beispiel für "freischalten" : im header wird, um die buffer-größen zu setzen der teil TOKEN_CONFIG des konfig-files "eingeblendet". so wird aus #define TOKEN_CONFIG #include PARSER_CONFIG_FILE #undef TOKEN_CONFIG -> #define SIZE_TOKEN_BUFFER 16 #define SIZE_VALUE_BUFFER 24 #define TOKEN_RESET_CHAR 0x1b eine ganz normale textersetzung des preprozessors also. nix besonderes. interessanter ist die mehrfachnutzung. beispiel für mehrfachnutzung : das makro TOKEN (und die "freischaltung des abschnitts") im konfig-file hat 3 parameter : name, text, attr (die namen sind hier etwas anders als im code, haben aber die selbe funktion) im header wird das makro TOKEN(name,attr,text) zu name, wird die konfig-datei nun zwischen ein enum { #include TOKEN_CONFIG_FILE }; "geladen", so wird nur der parameter name der token-tabelle eingeblendet. so wird durch die token-tabelle automatisch ein enum definiert. enum { TK_EOL, TK_EQUAL, TK_DOT, TK_SEP, TK_SPACE, TK_QUERY, TK_HEXVALUE, TK_STRINGVALUE, TK_LED, TK_ON, TK_OFF, TK_READ, TK_WRITE, TK_IO, }; (wenn man ganz konsequent wäre würde man daraus ein typedef machen, aber das verkompliziert die sache u.u. ungemein) in der c-datei hingegegen wird von dem makro TOKEN der parameter text und attr "verwurstet". aus #define TOKEN(name,attr,text) { attr, sizeof(text)-1, (UCHAR *) text }, und const TOKEN_DEF astTokenDefs [] = { #include TOKEN_CONFIG_FILE }; wird aus dem abschnitt #ifdef TOKEN : const TOKEN_DEF astTokenDefs [] = { { TC_END , sizeof("\x0d") -1, (UCHAR *) "\x0d" }, { TC_STD , sizeof("=") -1, (UCHAR *) "=" }, { TC_STD , sizeof(".") -1, (UCHAR *) "." }, { TC_STD , sizeof(",") -1, (UCHAR *) "," }, { TC_FOLLOW, sizeof(" ") -1, (UCHAR *) " " }, { TC_STD , sizeof("?") -1, (UCHAR *) "?" }, { TC_VALUE , sizeof("0x") -1, (UCHAR *) "0x" }, { TC_STRING, sizeof("\x34") -1, (UCHAR *) "\x34" }, { TC_STD , sizeof("LED") -1, (UCHAR *) "LED" }, { TC_STD , sizeof("ON") -1, (UCHAR *) "ON" }, { TC_STD , sizeof("OFF") -1, (UCHAR *) "OFF" }, { TC_STD , sizeof("READ") -1, (UCHAR *) "READ" }, { TC_STD , sizeof("WRITE")-1, (UCHAR *) "WRITE"}, { TC_STD , sizeof("IO") -1, (UCHAR *) "IO" }, }; durch die mehrfach-nutzung des selben makros mit unterschiedlichen "sichtweisen" lassen sich somit enums und daten mit "einem schlag" generieren. das schöne dabei ist das die reihenfolge von enums und erzeugten daten konsistent ist. wenn die reihenfolge der tokens geändert wird, passen enums und daten trotzdem zueinander. aber es geht noch mehr :-) durch verwendung des ##-operators innerhalb der makro-definition können verweise (über zeiger) gesetzt werden. beispiel : #define PARSE_LINES #define PARSE_BEGIN(name,func) UCHAR aucParseLine_##name [] = { #define PARSE_DEF(token) token, #define PARSE_END }; #include TOKEN_CONFIG_FILE erzeugt mit #ifdef PARSE_LINES PARSE_BEGIN (PARSER_LED_ON, vSetLED) PARSE_DEF (TK_LED) PARSE_DEF (TK_SPACE) PARSE_DEF (TK_ON) PARSE_END das array : UCHAR aucParseLine_PARSER_LED_ON [] = { TK_LED, TK_SPACE, TK_ON, }; da ein solch definiertes array namensmässig für sich alleine steht und code-mässig nur schwer zu erreichen ist, lässt sich über eine zeiger-tabelle, welche durch eine andere definition der makros PARSE_BEGIN, PARSE_DEF und PARSE_END erzeugt wird, ein recht einfacher zugriff (über vorher erzeugte enums oder einer variable) auf die arrays ermöglicht. definiert man die PARSE_xxx-makros folgendermaßen : #define PARSE_BEGIN(name,func) { sizeof (aucParseLine_#name), (UCHAR *) (aucParseLine_#name), (T_PARSE_EXEC) & func #define PARSE_DEF(token) #define PARSE_END so wird mit PARSE_LINE astParseLines [] = { #include TOKEN_CONFIG_FILE }; aus PARSE_BEGIN (PARSER_LED_ON, vSetLED) PARSE_DEF (TK_LED) PARSE_DEF (TK_SPACE) PARSE_DEF (TK_ON) PARSE_END -> PARSE_LINE astParseLines [] = { ... { sizeof (aucParseLine_PARSER_LED_ON), (UCHAR *) aucParseLine_PARSER_LED_ON, & (vSetLED) }, ... };
TheMason wrote: > durch die mehrfach-nutzung des selben makros mit unterschiedlichen > "sichtweisen" lassen sich somit enums und daten mit "einem schlag" > generieren. das schöne dabei ist das die reihenfolge von enums und > erzeugten daten konsistent ist. wenn die reihenfolge der tokens geändert > wird, passen enums und daten trotzdem zueinander. So, jetzt mal die Preisfrage, was habe ich davon ? Wenn ich ein Token einfüge, kriege ich erstmal nur ein weiteres Enum, was nirgends ausgewertet wird. Ich muß also an anderer Stelle erst noch ne Behandlung für das Enum einfügen. Und wenn ich ein Token lösche, kriege ich an dieser anderen Stelle nen Fehler wegen nem ungültigen Token. Ich finde es daher sinnvoller, nicht erst den Umweg über ein Enum zu gehen, sondern die Behandlungsfunktion direkt in der Tabelle mit anzugeben. Dann kann ich leicht Einträge einfügen und löschen, ohne an mehreren Stellen editieren zu müssen. Das erzeugt auch weniger und schnelleren Code. Peter
>kriege ich erstmal nur ein weiteres Enum, was nirgends ausgewertet wird. wieso ? die enums stehen doch für die schlüsselwörter (z.b. "LED", "ON", "OFF" usw.) die stehen zwar erstmal nur für sich, aber die werden doch im abschnitt PARSE_LINES mit dem makro PARSE_DEF verwendet. da werden die schlüsselwörter zu befehlen zusammengebaut. würde ich die enums nicht definieren, müsste ich, um z.b. "LED" " " "ON" als befehl zu definieren mir die indizes der jeweilgen token raussuchen und als magic numbers dahinschreiben, oder ? und das ist fehlerträchtig. vor allem wenn sich die reihenfolge ändert. und da enums keinen speicher brauchen "frisst" es doch nur ressourcen für den compiler, und der hat auf nem pc ja wohl mehr als genug. >Ich muß also an anderer Stelle erst noch ne Behandlung für das Enum >einfügen. nur wenn du das token verwenden möchtest. nur dann muß es in den parse-lines angegeben werden. die behandlung (abfrage) für das token wird über die parse-lines gemacht. nur diese werden (sobald das ende-token erkannt wird) mit den bisher erkannten tokens verglichen und bei übereinstimmung wird die funktion die bei der parse-line angegeben ist aufgerufen. >Und wenn ich ein Token lösche, kriege ich an dieser anderen Stelle nen >Fehler wegen nem ungültigen Token. sofern es verwendet wird ja. wenn nicht dann erzeugts auch keinen fehler. >Ich finde es daher sinnvoller, nicht erst den Umweg über ein Enum zu >gehen, sondern die Behandlungsfunktion direkt in der Tabelle mit >anzugeben. meinst du für jedes token eine behandlungsfunktion ?! dann könnte man pro befehl aber nur ein token angeben. beispiel : du möchtest byte-weise auf einen speicher oder io bereich zugreifen (lesen). du definierst dir schlüsselwörter "READ" "IO" "MEM". wenn du den speicher auslesen möchtest baust du dir den befehl mit den tokens READ und MEM auf der eine andere funktion bekommt wie der befehl fürs IO-lesen. den baust du dir über die tokens READ IO zusammen. bei beiden befehlen kannst du jeweils eine eigene funktion oder aber auch diesselbe funktion mit angeben da dieser funktion mitgeteilt wird welcher befehl erkannt wurde (über ein vorher generiertes enum). würdest du nur über ein token (also "IO" oder "MEM") den lese-vorgang starten so hättest du bei einer erweiterung (weil du von mir aus noch in den IO und MEM bereich was schreiben möchtest) ein linguistisches problem da du IO und MEM nicht mehr verwenden kannst, da diese schon jeweils eine eigene funktion fürs lesen bekommen haben. oder meintest du das nicht ? man mag freund oder feind der enums sein, aber die fressen keinen speicher, und ich arbeite lieber mit enums als mit magic numbers (vor allem wenn viele einträge erzeugt werden). außerdem hält man seine tabelleneinträge konsistent (zumindest wenn daten und enums vom präprozessor generiert werden). >Das erzeugt auch weniger und schnelleren Code. code wird hier nur in tabellen (also daten-form) erzeugt. da ist nichts schneller oder langsamer. ok es muß gesucht werden. das wars dann aber auch schon. und so langsam ist die suche nicht (zumal diese noch erheblich beschleunigt werden kann). ich hoffe ich habe deine einwände richtig verstanden. gruß rene
ich weiß zwar nicht obs nun komplizierter wird oder nicht, aber ich habe mal den output vom präprozessor bearbeitet und angehängt um zu zeigen an welchen stellen was ersetzt wird. so kann man wenigstens sehen welche enums und welche daten generiert werden. ich habe die kommentare mal drin gelassen. werde den output aber nochmal bearbeiten um nur den rein erzeugten code zu zeigen.
hier das ganze nochmal etwas kompakter. (ohne kommentare und näher zusammengerückt). ich habe (da unsigned char recht lang ist dieses durch byte ersetzt)
TheMason wrote: > meinst du für jedes token eine behandlungsfunktion ?! dann könnte man > pro befehl aber nur ein token angeben. > beispiel : > > du möchtest byte-weise auf einen speicher oder io bereich zugreifen > (lesen). > du definierst dir schlüsselwörter "READ" "IO" "MEM". > wenn du den speicher auslesen möchtest baust du dir den befehl mit den > tokens READ und MEM auf der eine andere funktion bekommt wie der befehl > fürs IO-lesen. den baust du dir über die tokens READ IO zusammen. Ja genau, bei meinem Programmbeispiel muß man eben die fertigen Token definieren, also "READIO", READMEM" usw. Und man hat auch sofort die zuständige Funktion ausgeführt, d.h. man muß nicht erst extra per switch/case die Tokennummer wieder auseinandertüdeln. Das Zusammenbasteln aus mehreren Teil-Token macht nur Sinn, wenn diese sehr lang sind aber nicht bei nur 2..4 Zeichen. Peter P.S.: Ich hab jetzt endlich rausgekriegt, daß Du die Token in parser_cfg.h zusammenbastelst, wird ja indirekt über 7 Ecken includiert.
>Ja genau, bei meinem Programmbeispiel muß man eben die fertigen Token >definieren, also "READIO", READMEM" usw. bei mir wird das dann über mehrere tokens gemacht. erscheint im ersten moment etwas aufwendiger (ist es auch) aber man muß nicht für jede neue funktion sich ein neues kommando ausdenken sondern kann mittels kurzer schlüsselwörter neue zusammenbauen. >Und man hat auch sofort die zuständige Funktion ausgeführt, d.h. man muß >nicht erst extra per switch/case die Tokennummer wieder >auseinandertüdeln. müssen muß man das bei mir auch nicht. es bleibt dem benutzer überlassen ob er für jede befehlszeile (parse_line) eine eigene funktion oder eine sammelfunktion machen möchte. es sollte nur ein beispiel sein. man kann natürlich eine funktion für "LED ON" und eine für "LED OFF" machen. aber ein kurzes switch case tut sicherlich weniger weh als 3 funktionen zu definieren die man dann auch noch eintragen muß. der hintergedanke mit dem switch case in einer funktion ist dann der das es man nur eine sammelfunktion hat (z.b. read_mem) und in dieser dann das gewünschte kommando ausführt (man muß ja nicht zwingend mit switch case arbeiten, man kann anhand der parse_line die man ja übergeben bekommt ermitteln ob es sich um lese/schreib aufgaben handelt und dann den datentyp [byte,word...] ermitteln und dann ausführen). >Das Zusammenbasteln aus mehreren Teil-Token macht nur Sinn, wenn diese >sehr lang sind aber nicht bei nur 2..4 Zeichen. oder wenn man sehr viele befehle hat die man aus wenigen schlüsselwörtern zusammenbauen kann. beispiele : read byte mem read word mem read long mem read float mem write byte mem write word mem ... read byte io read word io ... mmc read sector mmc init mmc dir mmc cd man benötigt nur schlüsselwörter read, write, mem, io, byte, word, long, float, mmc, sector, init, cd und dir und kann sich alle erdenklichen kombinationen zusammenbauen. wie machst du das denn in deinem interpreter wenn du mehr als einen parameter übergeben willst ? so wie ich das verstanden habe muß sich die aufgerufene funktion um die dekodierung der daten kümmern oder ? das würde bei mir entfallen. man bekommt direkt dekodierte daten zur ausführung bereitgestellt. >Ich hab jetzt endlich rausgekriegt, daß Du die Token in parser_cfg.h >zusammenbastelst, wird ja indirekt über 7 Ecken includiert. 7 ecken ? na ja ein bischen übertrieben :-)) es sind nur 2-3 ecken. aber das kapselt die ganze sache deutlich. es ist z.b. möglich die eigentliche token-erkennung vom parsen loszulösen und den eigentlichen tokenizer (der ja ohnehin irgendwo die definition der schlüsselwörter stehen haben muß, und das ist nur eine einzige stelle, abegsehen von den enums) für einen descend recurse parser beispielsweise zu verwenden. somit besteht der parser streng genommen aus 2 teilen. dem tokenizer und dem parser der erkannte token "auswertet". das einzige wo 2 ecken verwendet werden sind die parse-lines. aber da kommt man bei dem getrennten ansatz eh nicht drum rum, da die befehle alle unterschiedlich lang sein können und ich über ein einziges array zugriff auf alle parse-lines habe.
ein weiteres schmankerl zu dem mein parser fähig ist hatte ich rausgenommen. durch verwendung der tabellen bin ich nicht nur in der lage befehle zu erkennen, ich kann auch den umgekehrten weg gehen und alle möglichen (definierten) befehle auflisten. so hat man direkt eine kleine help-funktion die sich direkt in den parser integrieren lässt, sodass man sich über ein hilfe-schlüssel-wort erstmal "umschauen" kann was der parser denn alles kann. werde es evtl. wieder reinnehmen.
Hallo TheMason, Sehr schöne Lib, bin dabei ihn zu benutzten. Kannst das commando list feature verfügbar machen, währe ser schön für die hilfe funktion. Grüße Mark,
Hallo Mark, freut mich zu hören das der parser jemandem gefällt und dieser auch benutzt/benutzen will. Das Hilfe-Feature nehm ich wieder mit rein. Werde evtl. heute abend eine neue Version hochladen. Gruß Rene
hier eine version mit eingebauter hilfe. etwas zur konfiguration der hilfe : im abschnitt TOKEN_CONFIG kann die hilfefunktion mit #define TOKEN_USE_HELP eingeschaltet werden. dabei wird automatisch ein token HELP und eine parse line mit dem befehl help angelegt. der eigentliche abschnitt der die hilfe-ausgabe konfiguriert heißt TOKEN_HELP man kann dort folgende makros mit ausgabe funktionen belegen : TOKEN_HLP_OUTPUT_STR(x) string ausgeben TOKEN_HLP_OUTPUT_CRLF crlf ausgeben TOKEN_HLP_ATTR_CODEC_ST codec start ausgeben TOKEN_HLP_ATTR_CODEC_EN codec ende ausgeben TOKEN_HLP_SYNTAX_ERROR syntax error ausgeben TOKEN_HLP_ATTR_CODEC_ST und TOKEN_HLP_ATTR_CODEC_EN sind funktionen die evtl. definierte codecs "umrahmen". das umrahmen kann z.b. ein [ ] sein oder aber auch (im falle einer vt100 ausgabe) eine farbliche oder fettgedruckte ausgabe sein. weiterhin kann man beim benutzen der hilfe einen syntax error ausgeben lassen bei falsch eingegebenen befehlen. und das beispiel programm erlaubt jetzt auch eingaben :-) viel spaß damit
Hallo TheMason, Danke für die hilfe funktion. Grüße Mark,
Hallo TheMason, Ich hätte noch ein paar fragen: 1. wie kann ich dezimal zahlen ohen "0d" eingeben? 2. wie kann ich die strings bekommen ohne das alle groß geschrieben ist? Grüße Mark,
hallo mark, zu 1) direkt möglich ist das leider nicht, da der tokenizer immer ein einleitendes token erwartet. würde aber gehen wenn du die zahlen 0-9 als einleitendes token definierst und für alle 10 tokens den selben codec nimmst. du musst dann nur beim ersten ankommenden zeichen im codec die "zahl" (also das token) davor (welches du unter ucToken mit übergeben bekommst) wieder extrahieren. dann musst du allerdings dich auch um den hexadezimal teil kümmern, da das einleitende token 0 und 0x nicht mehr unterschieden werden können. ist etwas fummelig und ich habs selbst noch nicht ausprobiert da ich immer mit hex-zahlen arbeite (ist einfacher zu codieren :-)). zu 2) du musst in der funktion ucProcessTokenChar verhindern das ucChar überschrieben wird. UCHAR ucProcessTokenChar (UCHAR >>ucCharOrg<<) { ... ucChar = ucUpperCase (>>ucCharOrg<<); ... ... etwas weiter unten else { // we call the codec ... ucToken = stGlobalParse.pfCurrentParse ( EC_PROCESS, >>ucCharOrg<<, stGlobalParse.ucLastToken); ... } dann sollte es klappen. allerdings mußt du dich dann in den codecs um groß/kleinschreibung selbst kümmern.
TheMason wrote: > würde aber gehen wenn du die zahlen 0-9 als einleitendes token > definierst und für alle 10 tokens den selben codec nimmst. Verstehe ich. > du musst dann > nur beim ersten ankommenden zeichen im codec die "zahl" (also das token) > davor (welches du unter ucToken mit übergeben bekommst) wieder > extrahieren. Wie mache ich das. Ich verstehe das Teil nicht so recht. Wo bekomme ich ucToken übergeben? Habe ziemlich viel gesucht und bin nicht schlau draus geworden. Das einzige was ich verstanden habe, dass in dem Codec natürlich das einleitende Zeichen mit ankommen muß. Das tut es jetzt ja nicht. > dann musst du allerdings dich auch um den hexadezimal teil kümmern, da > das einleitende token 0 und 0x nicht mehr unterschieden werden können. Das ist mein Ziel. Ein Codec, der Hex, dec, und float kann. Bei 0x erkennt er hex, bei nur 1234 ist es dec. und bei 12.34 erkennt er float. Der Nachteil daran ist sicher, dass ich alle Zeichen zwischenpuffern muß da ich ja float nicht so ohne weiteres umwandeln kann. Na ja, Opfer müssen gebracht werden. Frage 2: Wie kann ich unterschiedliche Kommandos zu unterschiedlichen Zeiten zulassen? Z.B. in verschiedenen Menuebenen möchte ich in Ebene1 die KOmandos eins, zwei, drei und in Ebene2 vier, fünf, sechs zulassen. Der Help-Befehl sollte dann auch entsprechend arbeiten, was er vermutlich automatisch tun würde. Danke für die Info. Hajo
Hallo Hajo, ich beantworte Frage 2 zuerst, da diese leichter zu beantworten ist. Der Parser selbst arbeitet nicht "kontextsensitiv", von daher musst du selbst die zuordnung der Befehle zu deinem Menü machen, da mein Parser ja nicht deine Menü-struktur kennt bzw. nicht darin eingebettet ist. Sprich, du müsstest für jeden Befehl der unterschiedliche Auswirkungen haben kann, in deiner aufgerufenen Funktion eine unterscheidung (z.b. switch-case) machen die in abhängigkeit von deinem Menü-Punkt entsprechend reagiert. Gleiches gilt für die Help Funktion. Dieses listet nur die Befehle die der Parser kennt auf. Eine Beschreibung was der Befehl macht wird ja nicht mit abgeliefert. Somit kann auch keine kontextsensitive Hilfe gemacht werden. So, nun zu 1 : Ein Value-Codec hat folgende Schnittstelle : UCHAR ucXXXX (UCHAR ucOpcode, UCHAR ucChar, UCHAR ucLastToken) Mit Opcode wird gesagt was der Codec machen soll. Es kann 2 werte geben : EC_RESET und EC_PROCESS. Mit EC_RESET wird der Codec resettet. Hier kannst du zwischengespeicherte Daten (Text-Buffer und vorverarbeitete, bzw vorhergehende Ergebnisse) löschen. EC_RESET wird immer aufgerufen wenn ein neuer Wert per einleitendes Token verarbeitet werden soll. Mit EC_PROCESS bekommst du jedesmal ein neues Zeichen. Gleichzeitig sagt dir der Parameter ucLastToken mit welchem einleitenden Token diese Funktion aufgerufen wurde. Damit der Parser weiß wann die Zeichenfolge für diesen Codec ein Ende hat, musst du einfach das Token das zu dem zugehörigen Datentyp (Hex, Dez, Float, Bin, ...) zurückgeben. Du übergibst einfach das Token zurück das du ermittelt hast. Solange du noch am ermitteln bist (und dein Codec noch nicht fertig ist) gibst du TK_SEARCHING zurück. Das sagt dem Parser das er auch weiterhin noch Zeichen an den Codec liefern soll. Hat dein Codec einen Fehler entdeckt gibst du TK_NONE zurück und der Parser sucht weiter. Ist dein Codec fertig und das aktuelle Zeichen gehört schon zum weiteren Text so kannst du mit TK_UNGET den Codec beenden und das neue Zeichen vom Parser verarbeiten lassen. Du hast richtig erkannt das du dir einen Text-Buffer und einen Werte Buffer (für einen Hex, einen Dezimal und einen Float Wert) halten mußt mit dem du arbeiten kannst. Für deine vorgehensweise würde ich mir im Codec eine Statemachine bauen die erkennt um welchen Datentyp es sich handelt. Gleichzeitig landen alle angekommenen Zeichen in einem Buffer. Sobald deine Statemachine durchgelaufen ist konvertierst du den Eingangs Buffer zu einem Wert (von mir aus mit atoi, oder atof) und meldest dem Parser zurück welches Token du erkannt hast. So könnte es funktionieren (gebe allerdings keine Gewähr darauf). Noch ein kleiner Hinweis : Dieser Parser wird nicht mehr überarbeitet, da ich mittlerweile eine elegantere, kleinere (und evtl. schnellere) Variante des Parsers geschrieben habe. Diesen werde ich bald hier reinstellen. Gruß Rene
Hallo, erstmal danke für die sehr ausführliche Antwort. Allerdings habe ich es immer noch nicht begriffen :-( Siehe unten.... TheMason wrote: > Der Parser selbst arbeitet nicht "kontextsensitiv", von daher musst du > selbst die zuordnung der Befehle zu deinem Menü machen, da mein Parser > ja nicht deine Menü-struktur kennt bzw. nicht darin eingebettet ist. Soweit war mir das klar :-) > Sprich, du müsstest für jeden Befehl der unterschiedliche Auswirkungen > haben kann, in deiner aufgerufenen Funktion eine unterscheidung (z.b. > switch-case) machen die in abhängigkeit von deinem Menü-Punkt > entsprechend reagiert. Ja ist schon klar, nur an welcher Stelle Dein Parser auf die generierte Menustruktur zugreift, und wie man auch zwei unterschiedliche in dem parser_cfg.h-File generiert, dass ist mir nicht klar geworden. Also wo der Einsprung in die generierte "Tokentabelle" ist. Das sehe ich nicht. Das System mit den ganzen Enumeratoren, und Makros ist ja ziemlich ausgeklügelt, allerdings macht es das nicht einfacher zuverstehen. > Gleiches gilt für die Help Funktion. Dieses listet nur die Befehle die > der Parser kennt auf. Ja, das hatte ich mir schon so gedacht. > So, nun zu 1 : > > Ein Value-Codec hat folgende Schnittstelle : > > UCHAR ucXXXX (UCHAR ucOpcode, UCHAR ucChar, UCHAR ucLastToken) > > Mit Opcode wird gesagt was der Codec machen soll. Es kann 2 werte geben > : > EC_RESET und EC_PROCESS. > Mit EC_RESET wird der Codec resettet. Hier kannst du > zwischengespeicherte Daten (Text-Buffer und vorverarbeitete, bzw > vorhergehende Ergebnisse) löschen. > EC_RESET wird immer aufgerufen wenn ein neuer Wert per einleitendes > Token verarbeitet werden soll. Ist soweit verständlich. > Mit EC_PROCESS bekommst du jedesmal ein neues Zeichen. Gleichzeitig sagt > dir der Parameter ucLastToken mit welchem einleitenden Token diese > Funktion aufgerufen wurde. Ahhhhh... Klingelingeling! Das ist der Enumerator des Tokens? Das nehme ich jetzt an, da ich dort immer eine Zahl und kein ASCII-Zeichen gefunden habe. > Damit der Parser weiß wann die Zeichenfolge für diesen Codec ein Ende > hat, musst du einfach das Token das zu dem zugehörigen Datentyp (Hex, > Dez, Float, Bin, ...) zurückgeben. Du übergibst einfach das Token zurück > das du ermittelt hast. Den Sinn dafür verstehe ich nicht. TK_SEARCHING sagt dem Parser, er soll weiter den Codec aufrufen. Ist dem Parser an der Stelle dann nicht egal, was für ein Datentyp es ist? Er weiß doch davon garnichts (Hex, oder float oder...) sondern weiß nur er soll den Codec für eben Hex, float.... aufrufen. > Solange du noch am ermitteln bist (und dein Codec > noch nicht fertig ist) gibst du TK_SEARCHING zurück. Das sagt dem Parser > das er auch weiterhin noch Zeichen an den Codec liefern soll. Klaro. > Hat dein Codec einen Fehler entdeckt gibst du TK_NONE zurück und der > Parser sucht weiter. Auch klaro. > Ist dein Codec fertig und das aktuelle Zeichen gehört schon zum weiteren > Text so kannst du mit TK_UNGET den Codec beenden und das neue Zeichen > vom Parser verarbeiten lassen. Halb klaro. Z.B. Kommando: "SET VAL 1 2.34" Wenn der Codec die 1 durch hat und findet das ' ', dann muß ich schon TK_UNGET zurückgeben? > Sobald deine Statemachine > durchgelaufen ist konvertierst du den Eingangs Buffer zu einem Wert (von > mir aus mit atoi, oder atof) und meldest dem Parser zurück welches Token > du erkannt hast. Warum muß der Parser noch wissen, welches Token ich erkannt habe? Eine wichtige Frage habe ich aber immer noch. Wenn der Parser erkennt, dass er den Codec für die Zahlen (eben hex, dec, ....) aufrufen soll und er dass dann macht, dann fehlt mir das erste Zeichen der Zahl, da diese ja vom Parser gehamstert wurde als Tokeneinleitung. Gleichzeitig braucht die Codec Funktion ja dieses Zeichen, da es ja gleichzeitig die erste Ziffer der Zahl ist. Wie komme ich im Codec an dieses erste einleitende Zeichen? > > So könnte es funktionieren (gebe allerdings keine Gewähr darauf). Hey hey, wollte auch keine Garantieansprüche geltend machen. ;-) > Dieser Parser wird nicht mehr überarbeitet, da ich mittlerweile eine > elegantere, kleinere (und evtl. schnellere) Variante des Parsers > geschrieben habe. Diesen werde ich bald hier reinstellen. Hmmm.... Läßt der alte Parser sich denn so austauschen mit dem neuen und bleibt das Format der parser_cfg.h zum generieren der Tokens u.s.w. kompatibel? Danke für die Mühe. Gruß Hajo
Hallo nochmal, >Allerdings habe ich es immer noch nicht begriffen :-( Siehe unten.... macht nichts. verglichen mit meinem neuen parser ist das auch wirklich sehr kompliziert. (schande auf meinem haupt) >Das System mit den ganzen Enumeratoren, und Makros ist ja ziemlich >ausgeklügelt, allerdings macht es das nicht einfacher zuverstehen. nett formuliert !! hättest aber auch schreiben können : "das ist ja ein komplizierten krams ..." trifft den kern wohl eher wie ich mittlerweile auch eingesehen habe .... (daher auch parser #2 :-), aber nicht nur deswegen) wohl noch eben eine kurze frage : was meinst du mit menüstruktur ? den aufbau meiner token-tabellen ? ich hatte das so verstanden das du einen teil geschrieben hast der eine menüdarstellung (z.b. auf lcd oder vt100) macht und du möchtest das mit meinem parser verheiraten. richtig oder falsch ? >und wie man auch zwei unterschiedliche in dem >parser_cfg.h-File generiert, dass ist mir nicht klar geworden. du möchtest 2 unabhängige befehlssätze (also von mir aus einmal 4 befehle und einmal 10 befehle die gleich oder unterschiedlich sein können aber andere funktionen aufrufen) definieren, richtig ? das geht nicht. du kannst immer nur einen befehlssatz implementieren, denn es gibt nur eine token-tabelle in der alle schlüsselworte definiert sind, und (und das ist wichtig) auch nur eine befehls-zeilen tabelle. >Also wo der Einsprung in die generierte "Tokentabelle" ist. zu befehlen (bzw. befehlszeilen) zusammengebaut werden diese in den abschnitten PARSE_LINES der parser.c. da wird für jeden befehl ein array aufgebaut mit den tokens (byte-weise) die die befehlszeile bilden. du hast für jeden zusammengesetzten befehl also ein byte-array. so wird aus : PARSE_BEGIN (PARSER_LED_ON, vSetLED) // "LED ON" PARSE_DEF (TK_LED) // "LED" PARSE_DEF (TK_SPACE) // " " PARSE_DEF (TK_ON) // "ON" PARSE_END -> UCHAR aucParseElement_##parse [] = { TK_LED, TK_SPACE, TK_ON, }; alle diese aucParseElement_XXX werden in einem zeiger-array zusammengefasst sodass man einfach per index alle befehlszeilen durchackern kann. >Ahhhhh... Klingelingeling! Das ist der Enumerator des Tokens? Das >nehme ich jetzt an, da ich dort immer eine Zahl und kein ASCII-Zeichen >gefunden habe. nicht ganz. der parameter ucOpcode ist nur der Befehl (EC_RESET, EC_PROCESS) an den Codec ob er sich resetten soll bzw. ein zeichen zu bearbeiten hat. das zu verarbeitende ASCII-Zeichen wird in ucChar angeliefert. das einleitende token (welches diesen codec erst aufruft) wird im parameter ucLastToken mitgegeben. >Den Sinn dafür verstehe ich nicht. TK_SEARCHING sagt dem Parser, er soll >weiter den Codec aufrufen. Ist dem Parser an der Stelle dann nicht egal, >was für ein Datentyp es ist? nicht ganz. der parser muß ja wissen wann er wieder die kontrolle über den datenstrom bekommt. wenn du z.b. einen string-codec schreibst ist dem parser unbekannt wie lang der string ist. der codec weiß es zwar auch nicht, aber dafür weiß dieser wann der string zuende ist (nämlich dann wenn er auf ein " stösst). und solange wird eben gesucht (TK_SEARCHING) eine hexa zahl hingegen kann ja z.b. 0x0 oder aber auch 0x12345678 sein. das weiß weder parser noch codec, aber der codec kann dem parser sagen wann die erkennung des wertes abgeschlossen ist. >Wenn der Codec die 1 durch hat und findet das ' ', dann muß ich schon >TK_UNGET zurückgeben? richtig. es könnte ja sein das statt dem ' ' ein ',' steht und dieses noch weiter erkannt werden muß. >Warum muß der Parser noch wissen, welches Token ich erkannt habe? das liegt darin begründet das sich mehrere einleitende tokens einen codec "teilen" können. ursprünglich habe ich das verwendet um hex-zahlen bestimmter länge (1byte, 1word, 1long) über einen einzigen codec laufen lassen, und damit der codec weiß "wodurch" (vielmehr durch welches token er aufgerufen wurde) habe ich kurzerhand das einleitende token mitübergeben. >dann fehlt mir das erste Zeichen der Zahl den bekommst du ja das token in ucLastToken mit übergeben. wenn dein werte codec die einleitenden tokens 0-9 hat und dieser wird aufgerufen, steht in ucLastToken das token drin. du musst jetzt nur noch die zuordnung zu den zahlen 0-9 wiederherstellen. that's it ! >Hmmm.... Läßt der alte Parser sich denn so austauschen mit dem neuen und >bleibt das Format der parser_cfg.h zum generieren der Tokens u.s.w. >kompatibel? leider nicht, da der neue parser buffer-orientiert ist, also den kompletten befehl schon im speicher stehen haben muß und nicht wie bei diesem parser in dem zeichen für zeichen ermittelt wird was gemeint ist. Bei dem Parser hat man nur noch 1 Funktion + x Value Codecs (ja ja von den dingern lasse ich nicht ab :-), die sind da aber einfacher). Allerdings spiele ich da "Herr der Zeiger". Man sollte also mit Zeigern auf Zeigern klarkommen. Da werden die Befehlszeilen auch nicht mehr umständlich über Token und Token-Lines generiert sondern werden direkt als ASCII definiert. >Danke für die Mühe. kein thema. bin ja froh um jedes bischen feedback. gruß rene ps : ich bin mir mittlerweile nicht sicher ob sich dein universeller zahlen-codec so ohne weiteres implementieren bzw. anbinden lässt. das erkennen und aufbauen der zahl ist nicht das problem. ich denke es kann problematisch werden mit dem token-rückgabe wert. dieser wird ja explizit bei einer befehlszeile erwartet. evtl. kommst du nicht drum herum ein generelles zahlen token einzuführen (z.b. #) mit dem du dann deine zahlen erkennung einleitest.
TheMason wrote: > >>Das System mit den ganzen Enumeratoren, und Makros ist ja ziemlich >>ausgeklügelt, allerdings macht es das nicht einfacher zuverstehen. > > nett formuliert !! > > hättest aber auch schreiben können : "das ist ja ein komplizierten krams > ..." > trifft den kern wohl eher wie ich mittlerweile auch eingesehen habe .... > (daher auch parser #2 :-), aber nicht nur deswegen) Nein nein, ich meinte es schon so. Die Makros sind schon ziemlich ausgeklügelt eingesetzt. Aber es stimmt, es erzeugt schwer verstehbaren Code, was dann eine nicht so positive Wertung bekommt, wenn man es bewerten möchte. > > wohl noch eben eine kurze frage : > > was meinst du mit menüstruktur ? den aufbau meiner token-tabellen ? ich > hatte das so verstanden das du einen teil geschrieben hast der eine > menüdarstellung (z.b. auf lcd oder vt100) macht und du möchtest das mit > meinem parser verheiraten. richtig oder falsch ? Richtig! Und im Menu1 möchte ich nur eben nur einen Teil der Kommandos vom Parser zulassen und im Menu2 dann den anderen Teil der Kommandos. Das Menu kann sich auf LCD oder auch auf einem Terminal befinden. > >>und wie man auch zwei unterschiedliche in dem >>parser_cfg.h-File generiert, dass ist mir nicht klar geworden. > > du möchtest 2 unabhängige befehlssätze (also von mir aus einmal 4 > befehle und einmal 10 befehle die gleich oder unterschiedlich sein > können aber andere funktionen aufrufen) definieren, richtig ? Ja. > > das geht nicht. du kannst immer nur einen befehlssatz implementieren, Nee nee nee. So einfach kommst Du mir nicht davon ;-) Z.B. in meiner naiven Vorstellung: Ich generiere parser_cfg1.h und parser_cfg2.h. Beide binde ich in dem parser ein. Ich muß nur für unterschiedliche Arraynamen sorgen und dafür, dass der parser zur gegebenen Zeit (in einem bestimmten Menu eben) das richtige Array nutzt. > einleitende token (welches diesen codec erst aufruft) wird im parameter > ucLastToken mitgegeben. Das hatte ich verstanden. Aber wie komme ich von dem ucLastToken auf das Zeichen zurück? Ich möchte eben nicht, das ein einleitendes Zeichen verschluckt wird. >>weiter den Codec aufrufen. Ist dem Parser an der Stelle dann nicht egal, >>was für ein Datentyp es ist? > > nicht ganz. der parser muß ja wissen wann er wieder die kontrolle über > den datenstrom bekommt. Ja auch as habe ich schon verstanden. Der Parser braucht aber nicht den Datentyp wissen, sondern nur, wann er wieder Kontrolle übernehmen soll, also bei nicht TK_SEARCHING. > >>Warum muß der Parser noch wissen, welches Token ich erkannt habe? > > lassen, und damit der codec weiß "wodurch" (vielmehr durch welches token > er aufgerufen wurde) habe ich kurzerhand das einleitende token > mitübergeben. Ja. Schon klar. Ich hatte auch gefragt, warum der Parser und nicht der Codec wissen muß, welches Token er erkannt hat. Dem Parser ist es doch sicher egal. Er ruft den Codec auf, der muß mit dem Teilstring dan klarkommmen und dem Parser nur sagen, jetzt mach mal weiter, ich habe alles eingesammelt. > >>dann fehlt mir das erste Zeichen der Zahl > > den bekommst du ja das token in ucLastToken mit übergeben. > wenn dein werte codec die einleitenden tokens 0-9 hat und dieser wird > aufgerufen, steht in ucLastToken das token drin. du musst jetzt nur noch > die zuordnung zu den zahlen 0-9 wiederherstellen. that's it ! Eben so hatte ich das schon verstanden. Aber wie komme ich von dem Token zurück auf das Zeichen? >>Hmmm.... Läßt der alte Parser sich denn so austauschen mit dem neuen und > > leider nicht, da der neue parser buffer-orientiert ist, Hmmm.... genau das war der Grund, warum ich diesen Parser genommen habe, er braucht keine Buffer. > Allerdings spiele ich da "Herr der Zeiger". Man sollte also mit Zeigern > auf Zeigern klarkommen. Das ist eigentlich auch keine gern gesehene Art. So ein wilde Zeigerverknüpfung macht auch alles unübersichtlich. Das wird dann wieder Punktabzüge geben, so wie jetzt bei den ausgeklügelt eingesetzten Makros. > Da werden die Befehlszeilen auch nicht mehr umständlich über Token und > Token-Lines generiert sondern werden direkt als ASCII definiert. Das mag dann wiederum einfacher sein. > problematisch werden mit dem token-rückgabe wert. dieser wird ja > explizit bei einer befehlszeile erwartet. Was meinst du genau mit token-rückgabewert? Von welcher Befehlszeile wird der erwartet? Nix versteh :-( Danke nochmal Gruß Hajo
Hallo Hajo, >Nee nee nee. So einfach kommst Du mir nicht davon ;-) ohje ... :-) >Ich generiere parser_cfg1.h und parser_cfg2.h. Beide binde ich in dem >parser ein. Ich muß nur für unterschiedliche Arraynamen sorgen. also ich sag mal vorsichtig es kann möglich sein. aber es ist nicht einfach. es hängt meine ich noch etwas mehr daran. jedenfalls dürfte es etwas knifflige arbeit sein. >Aber wie komme ich von dem ucLastToken auf das >Zeichen zurück? Ich möchte eben nicht, das ein einleitendes Zeichen >verschluckt wird. beim ersten aufruf des value codecs mit EC_PROCESS bekommst du ja das 2. zeichen nach dem einleitenden token. das ist richtig, aber das einleitende token selbst (!!) ist doch das 1. zeichen. du musst es über eine switch-case oder kleinen tabelle auf eine zahl umlegen. sprich du gibst 456 ein und der codec wird mit dem einleitenden token 4 und dem ersten zeichen 5 aufgerufen. wenn du über ucLastToken jetzt auf 4 "rückrechnest" hast du dein erstes zeichen. oder hab ich da was übersehen ?! >Dem Parser ist es doch >sicher egal. Er ruft den Codec auf, der muß mit dem Teilstring dan >klarkommmen und dem Parser nur sagen, jetzt mach mal weiter, ich habe >alles eingesammelt. der datentyp selbst interessiert den parser ja auch nicht. er muß nur wissen welches token der codec erkannt hat damit der parser dieses auf den stack packen kann, da ja erst nach erfolgreicher erkennung das token auf den stack gepackt werden kann. > problematisch werden mit dem token-rückgabe wert. dieser wird ja > explizit bei einer befehlszeile erwartet. ist etwas blöd ausgedrückt ... ich meine damit folgendes : wenn du für jede zahl von 0-9 ein einleitendes token definierst, und eine befehlszeile z.b. "led" " " "state" "0" ist, kannst du nicht z.b. "led state 423" oder "led state 1" eingeben da ja explizit eine "0" erwartet wird, selbst wenn du über den value codec alle zahlen zulässt. wenn du aber JEDE zahl für deinen value-codec mit 0 einleitest (also 0x1234 012345, 010.456 müsste es gehen. also für deinen zahlen-codec würde ich immer als erstes eine 0 voraussetzen und dann erst die eigentliche zahl (egal welches format), sonst kann es probleme geben bzw. nicht erwartungsgemäß funktionieren. gruß rene ps. muß mich mal ganz artig bei dir bedanken für dein feedback. wenigstens einer der was mit dem parser anfangen kann :-) bzw. ein einsatz für den bufferless parser hat :-) halt mich mal auf dem laufenden bzw. sag mal wofür du den parser einsetzt
Hallo Rene, also weiter :-) > sprich du gibst 456 ein und der codec wird mit dem einleitenden token 4 > und dem ersten zeichen 5 aufgerufen. > wenn du über ucLastToken jetzt auf 4 "rückrechnest" hast du dein erstes > zeichen. > oder hab ich da was übersehen ?! Ich glaub das war anders. Muß ich nochmal prüfen. Ich meine, er hat nie in ucLastToken das erste Zeichen übergeben, sondern wahrscheinlich den Enumerator des Tokens. Prüfe ich noch. > der datentyp selbst interessiert den parser ja auch nicht. er muß nur > wissen welches token der codec erkannt hat damit der parser dieses auf > den stack packen kann, da ja erst nach erfolgreicher erkennung das token > auf den stack gepackt werden kann. Ich denke er muß nur wissen erfolgreich oder nicht?!?! > ich meine damit folgendes : wenn du für jede zahl von 0-9 ein > einleitendes token definierst, und eine befehlszeile z.b. "led" " " > "state" "0" ist, kannst du nicht z.b. "led state 423" oder "led state 1" > eingeben da ja explizit eine "0" erwartet wird, selbst wenn du über den > value codec alle zahlen zulässt. Ach ja, jetzt hab ich es auch gemerkt. Was ich will, geht natürlich so garnicht, da man nicht sagen kann 0-9 ist ein Token für ein Befehl. > wenn du aber JEDE zahl für deinen value-codec mit 0 einleitest (also > 0x1234 012345, 010.456 müsste es gehen. Klar, hab ich verstanden. Genau das wollte ich aber nicht. Ich mag Eingaben wie SET PAR1 3.137 und nicht SET PAR1 0f3.137 > ps. muß mich mal ganz artig bei dir bedanken für dein feedback. Quark, danke für den Code. :-) > wenigstens einer der was mit dem parser anfangen kann :-) bzw. ein > einsatz für den bufferless parser hat :-) Na ja warten wir mal ab. Also ich finde ihn gut, aber er hat eben auch Nachteile gerade in einem AVR. Der Parser frißt doch viel Speicher. Was ich gut fand ist die flexible Zusammensetzung der Befehle mit MEHREREN Parametern und das er ohne Buffer auskommt. Das Beispiel von Peter oben finde ich auch nicht schlecht, da er eben sehr kurz und einfach zu vertehen ist. Braucht aber Buffer und die Befehle (Tokens) kann man nicht ganz so schön zusammensetzen. Aber wer schön sein will muß leiden, das war schon immer so ;-) > halt mich mal auf dem laufenden bzw. sag mal wofür du den parser > einsetzt Also ich wollte ihn jetzt sozusagen ausprobieren in einer Lötstation zum setzen von Reglerparametern u.s.w. Letztendlich wollte ich auch Erfahrung mit dem Ding sammeln um damit die gleichen Dinge zu tun wie Du. Nämlich in unterschiedlichen Projekten zum Testen eine Kommandozeile zu haben. Na mal sehen was ich mache. Also ich werde mich wohl erstmal an SET PAR1 0f3.137 gewöhnen müssen und dann mal sehen ob ich den Speicher hergeben will. Deinen neuen Parser hab ich noch nicht untersucht, keine Zeit. Ich will es auch ohne Buffer haben, es sei denn der neue Parser hat andere Vorteile. Danke für Deine Unterstützung. Gruß Hajo
Hallo Hajo, >Ich meine, er hat nie in ucLastToken das erste Zeichen übergeben, sondern >wahrscheinlich den Enumerator des Tokens. Ja. Es ist der enumerator des Tokens, aber da du ja weißt welcher Enumerator für welche Zahl steht kannst du "rückrechnen". >Ich denke er muß nur wissen erfolgreich oder nicht?!?! Wie gesagt war der hintergedanke das sich mehrere einleitende Tokens einen Codec "teilen" können. und in diesem fall gibt der codec den erkannten datentyp zurück. >Ich mag Eingaben wie SET PAR1 3.137 >und nicht SET PAR1 0f3.137 ich meinte auch nicht das du explizit 0f nehmen mußt sondern es würde ja schon 0 reichen. dann kannst du über die 0 als einleitendes token weitere prüfungen vornehmen und feststellen ob es sich um eine Hex,Dez oder float zahl handelt, also statt SET PAR1 3.137 SET PAR1 03.137, oder statt SET PARx 1234 SET PARx 01234. sieht zwar auch nicht viel schöner aus, aber so müsstest du nicht explizit 0x, 0f, 0d angeben und es lässt sich noch einigermassen einfach eingeben ... >Also ich finde ihn gut, aber er hat eben auch >Nachteile gerade in einem AVR ursprünglich habe ich den parser auf einem msp430 laufen lassen. da war es etwas einfacher (durch die durchgängig lineare adressierung von ram und flash), und hatte auch noch einige andere ideen (dynamischer-befehlssatz), habe diese aber nachher wieder verworfen (2k ram sind eben doch recht wenig :-)) aber freut mich trotzdem das du mit dem code was anfangen kannst. hoffe das du diesen code für deine lötstation und weitere projekte brauchen kannst. gruß rene
kurze frage noch dazu, konnte noch nicht alles lesen in diesem Beitrag :-( ist das Teil auch fähig, mit Umbau, XML Dateien zu lesen? grüssle hans
@hans also für xml braucht man sehr viel speicher oder einen pfiffigen algorithmus. ansatzweise wäre es mit dem nachfolger dieses parsers möglich, aber es dürfte am speicher scheitern.
Hallo, eine Frage, ist eine Implementation mittels Hash von interesse, ohne Möglichkeit einer Hilfe.
@chris hash bietet sich in erster linie beim suchen in dynamischen (also nicht zur compilierzeit festgelegten) daten an. um das im parser nutzen zu können müsste man ein pc-tool haben das aus den schlüsselwörtern die hashs errechnet. wäre geschwindigkeitsmässig evtl. ein vorteil, aber ob sich das lohnt weiß ich nicht. zumal man bei jeder änderung der schlüsselwörter/sätze das pc tool anschmeißen müsste. so kann man einfach im quellcode die neuen schlüsselwörter/sätze eingeben
Vorteil ist, daß man nur 2 byte je schlüsselwort braucht. Es geht auch ohne PC tool, mit dem Preprocessor, z.B. Trotzdem, es stimmt, bin von Unix mit m4 usw verwöhnt #define chr(x) ((x)-32) #define H(x) *9+chr(x)) #define HASH(x) (0 x) #define HASH3(a,b,c) HASH( ((( H(a) H(b) H(c) ) #define HASH4(a,b,c,d) HASH( (((( H(a) H(b) H(c) H(d)) #define HASH(n,a...) HASH##n(a) #define TOK_LED HASH(3,'L','E','D')
@chris >#define HASH4(a,b,c,d) HASH( (((( H(a) H(b) H(c) H(d)) >#define HASH(n,a...) HASH##n(a) geht das mit dem ganz normalen c-präprozessor ?! oder ist das m4 ?
Es geht mit normalen cpp, obwohl das obige Beispiel nicht funktioniert, hatte es nur so schnell hingetippt. Hier ist der relevante code aus einem funktionierendem header file. #ifdef USE_MUL #define hash_mul_10 (hash_*10) #define hashc(c) hash_=hash_*9+c #else #define hash_mul_10 (hash_<<3+hash_<<1) #define hashc(c) hash_=hash_<<3+hash_+c // hash_=hash*9+c #endf #define chr_offset 32 #define H(x) *9+chr(x) #define chr(x) ((x)-chr_offset) #define HASH_(a,b,c,d) (((a H(b))H(c))H(d)) #define HASH__(_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e,_f) \ HASH_( Hash_( 0,_1,_2,_3), \ HASH_(_4,_5,_6,_7), \ HASH_(_8,_9,_a,_b), \ HASH_(_c,_d,_e,_f) \ ) #define HASH16( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e,_f,x) \ ((HASH__(_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e,_f)H(n))*9+n) #define HASH15( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e,_f) \ (HASH__(_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e,_f)*9+n) #define HASH14( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e)\ HASH15( n,0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d,_e) #define HASH13( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d) \ HASH14(n, 0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c,_d) #define HASH12( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c) \ HASH13(n, 0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b,_c) #define HASH11( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b) \ HASH12(n, 0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b) #define HASH10( n,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a) \ HASH11(n, 0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a) #define HASH9( n,_1,_2,_3,_4,_5,_6,_7,_8,_9) \ HASH10(n, 0,_1,_2,_3,_4,_5,_6,_7,_8,_9) #define HASH8( n, _1,_2,_3,_4,_5,_6,_7,_8) \ HASH9( n, 0,_1,_2,_3,_4,_5,_6,_7,_8) #define HASH7( n,_1,_2,_3,_4,_5,_6,_7) \ HASH8( n, 0,_1,_2,_3,_4,_5,_6,_7) #define HASH6( n,_1,_2,_3,_4,_5,_6) \ HASH7( n, 0,_1,_2,_3,_4,_5,_6) #define HASH5( n,_1,_2,_3,_4,_5) \ HASH6( n, 0,_1,_2,_3,_4,_5) #define HASH4( n,_1,_2,_3,_4) \ HASH5( n, 0,_1,_2,_3,_4) #define HASH3( n,_1,_2,_3) \ HASH4( n, 0,_1,_2,_3) #define HASH2( n,_1,_2) \ HASH3( n, 0,_1,_2) #define HASH1( n,_1) \ HASH2( n, 0,_1) #define HASH(n,x,...) HASH##n(n,x)
Wie gesagt, der vorteil im uC ist, daß man sich platz im Flash spart, sowie auch Ram, die Geschwindigkeit ist nicht so relevant. Die Idee dahinter ist, eine 16bit variable nach BSD standard hashen, und dann noch die länge des wortes als hash hinzuzählen. Effektiv werden so die letzten 3-4 Zeichen sowie die Länge des Wortes als Hash hergenommen und eine Addition einer Quersumme. Das mit der Wortlänge könnte auch weggelassen werden. Da man wie gesagt je identifier nur 2 bytes braucht, spart man sich hauptsächlich flash, wenn man relativ viele Tokens hat.
@TheMason (Gast) Könntest du bitte mal Groß- und Kleinschreibung benutzen? Deine Beiträge sind eine Zumutung!
Hey, sowas ähnliches hab ich auch mal gemacht, siehe Anhang. Allerdings nicht so kompliziert. Kommt mit ~80 Bytes RAM aus. Ich hab es mehr als "richtige" Shell gedacht, d.h. Argumente an die Unterfunktionen werden über standard-argc/argv übergeben.
> command_t mycmds
und die Strings könnten noch ins ROM statt ins RAM ;)
Wie ins ROM? Warum soll ich das Zeug denn ins Flash-BIOS schreiben? Spaß beiseite, ich hatte das auf dem PC geschrieben. Auf dem uC hatte ich noch keine richtige Verwendung dafür. Als Steuer-/Debugschnittstelle aber sicher nützlich. Übrigens geht das ganze in Python sehr elegant:
1 | import sys |
2 | |
3 | def printhelp(s): |
4 | """Print a list of available commands.""" |
5 | cmds = commands.keys() |
6 | cmds.sort() |
7 | for c in cmds: |
8 | print c + '\t' + commands[c].__doc__ |
9 | |
10 | def exit(s): |
11 | """Exit this shell.""" |
12 | print 'closing connection' |
13 | msp = '' |
14 | raise SystemExit |
15 | |
16 | def test(s): |
17 | """Print arguments.""" |
18 | if len(s) > 1: |
19 | print "there were " + str(len(s)) + " arguments" |
20 | else: |
21 | print "there was 1 argument" |
22 | |
23 | print s |
24 | |
25 | commands = {'?':printhelp, |
26 | 'x':exit, |
27 | 'test':test |
28 | } |
29 | |
30 | while 1: |
31 | sys.stdout.write('>> ') |
32 | s = sys.stdin.readline() |
33 | if s.split() == []: continue |
34 | if s.split()[0] not in commands: |
35 | print 'invalid command' |
36 | continue |
37 | commands[s.split()[0]](s.split()) |
Das Prinzip ist das gleiche.
Marko wrote: > Wie ins ROM? Warum soll ich das Zeug denn ins Flash-BIOS schreiben? Hehe. > ...python code... > Das Prinzip ist das gleiche. Das braucht aber sicher mehr als 80 Byte RAM :D
> Das braucht aber sicher mehr als 80 Byte RAM :D
Sehr gut beobachtet. Du mußt Dipl.-Inf. sein ... ;)
@georg sorry, aber da hast du pech gehabt ... [pamp-modus on] ich schreibe wie ich es meine/denke (wenn du dich mit jemandem unterhälst betonst du großgeschriebene wörter doch auch nicht oder ?) und ein bischen flexibilität darf man doch wohl erwarten. immerhin schreibe ich alle wörter (fast) immer richtig, und bediene mich auch des kommas und des punktes. das muß reichen. sonst wird noch gemosert wenn man groß/kleinschreibung falsch benutzt ... [pamp-modus off] @chris vielen dank für das beispiel. wusste gar nicht das variable argumente mit dem normalen präprozessor möglich sind. das eröffnet mir völlig neue möglichkeiten, und viele neue schweinereien :-))) das einzige was mir an der hash methode nicht so gefällt, ist die tatsache das u.u. durch falsch geschriebene befehle irgendwelche anderen befehle ausgeführt werden und man leider nicht immer dahinterkommt was genau ausgeführt wurde. ansonsten ist die hash methode sicherlich interessant. vielleicht bau ich das in meinem dritten parser ein :-)) @alle danke für die beteiligung an dem thread. jetzt kommt mal etwas leben hier rein :-)) @marko werd mir deinen code mal zu gemüte führen.
Ich hätte einen Parser mit der Hash-Technik rumliegen, der im Pic 50-100 bytes benötigt, je nach Umfanges des Wortschatzes, müsste jedoch auf AVR oder dergleichen portiert werden, (C code). Bezüglich des falschschreibens, der Hash ist sehr effektiv, habe ihn z.B. in einem Lisp-Interpreter im Einsatz, mit einigen hundert von Lisp-Zeilen, die der Benutzer ladet,und auch interactiv benutzt. Bei einigen hunderten von Installationenist noch kein Fall von Doppelbelegung oder Falschinterpretation herausgekommen.
Wow, nach 3 bzw 4 Jahren wird der Thread mal wieder hochgeholt. Cool :-) Also, bei meinem Parser hat sich so gesehen vieles getan, bzw vieles entwickelt. Es gibt mehrere neue Versionen bzw Anwandlungen die ich im Laufe der Zeit benutzt hab und die recht stabil sind, aber hier wirklich was neues reingestellt hab ich nach der zweiten Version (siehe weiter oben) nicht mehr. Allerdings habe ich beim AVR-BASIC-Projekt einen richtig schnellen Parser bzw Tokenizer geschrieben (allerdings mit PC-Tool). Den könnte man vllt mal was umbauen. http://www.mikrocontroller.net/articles/AVR_BASIC Beitrag "uParse - die zweite." Aber schreib mal genau was du möchtest bzw brauchst. Vllt hab ich da schon was fertig. Oder schick ne PN.
Hi Rene, in dem mini Thread den ich geöffnet habe steht drin was ich vor hatte: Beitrag "Wie müssen die Register-Daten bei einer UART Kommunikation aussehen?" Es ging daraum, via RS232 vom Pc aus Funktionen auszulösen im Controller z.B. das wenn ich 0x00 0x0A sende dann der Controller mir einen aktuellen Wert sendet oder bei einem anderen die Version ausgibt usw. Wäre das was? Es sind insgesammt 20 Kommandos die ich benötige.
Hi Timo, also prinzipiell machen meine Parser folgendes : Du hast irgendwo Definitionen abgelegt wie deine Befehle aussehen bzw heißen. Wenn du nun mit einem Buffer angelaufen kommst und prüfen willst ob der Bufferinhalt zu einem Befehl passt dann rufst du einfach nur eine Funktion auf und der sucht dir (falls alles richtig geschrieben ist) den entsprechenden Befehl raus und (falls Daten mit angeben sind) die enstprechenden Daten. Also das ganze ist quasi eine Umsetzung von Text->Binär. Beispiel Nr Befehl 1 print [string] 2 print [nummer] 3 version 4 help [string] 5 help 6 read [nummer] 7 write [nummer],[nummer] 8 [nummer] = [nummer] Würdest du mit einem Buffer mit dem Inhalt "write 0x45, 0x23" ankommen würde mein Parser sagen : Befehl 7 mit Parameter 0x45 und 0x23. Bei "print -45" würdest du gesagt bekommen Befehl 2 mit Parameter -45. Bei "123 = 0xff" -> Befehl 8 mit 123 und 0xff. Die Zahlen oder Strings werden dir dann direkt richtig binär vorgekaut angeliefert werden, und (ist bei meinen Parsern so üblich) du wirst bei richtig erkanntem Befehl aufgerufen, bzw kannst eine von mehreren Funktionen aufrufen lassen (wenn z.b. mehrere befehle für einen "Themenkomplex" existieren die man am sinnigsten logisch zusammenfasst). Den Parser kann man so einstellen das Groß/Kleinschreibung ignoriert wird. Zusätzliche Spaces oder Tabs werden ignoriert. Und es gibt eig immer eine Hilfe Funktion, mit der man sich die definierten Befehle auflisten kann (so ähnlich wie im Beispiel beschrieben). Im Grunde ist das ganze für eine Shell geeignet. Aber dadurch das der Parser eben "nur" nach den richtigen Befehlen sucht und ggf Parameter extrahiert bzw aufbereitet ist das auch universell einsetzbar. So benutze ich den Parser gerne auch zum durchlaufen von Programmargumenten unter Windows, oder wenn ich ein Skript Zeilenweise einlese um beispielsweise Einstellungen oder Konfigurationen einzulesen. Oder als einfacher Assembler eignet sich das auch hervorragend. Oder eben wenn man Klartext-Befehle von einem Gerät bekommt das an einer seriellen Schnittstelle angeschlossen ist, und man diese Befehle auswerten will. Es sollte nur klar sein das der Parser nur reinen Text "beherrscht", sprich das wenn man wirklich 0xaa oder 0xbb als einzelnes Zeichen schickt, wüsste der Parser auch erstmal nix damit anzufangen (es sei denn ich lasse alls Zeichen oberhalb von 128 zu und würde jeweils einen einzelnen Befehl mit 0xaa und 0xbb definieren. Aber wenns nur einzelne Zeichen sind ist dieser Parser mit Spatzen auf Kanonen geschossen :-) Aber für Text->Binär bzw Befehl [Daten, Daten] -> Binär umsetzung sind meine Parser so ziemlich alle geeignet. Vllt stell ich die Tage mal eine aktuelle Version rein. Bekommst du denn wirklich 0xAA (als ASCII-Text) oder als Zeichen geliefert ?
Hoert sich interessant an! Nein ich hab das selbst so definiert. Ich kann selbstverstaendlich auch normale Zeichen verwenden, das liegt an mir. Ich hab das nur in hex gemacht da ich mit HTERM arbeite aber der kann auch normale zeichen versenden. Ist also kein Problem
Also ich hab in diesem bzw dem anderen Thread schonmal erklärt warum ich das mache. Ich finds eigentlich immer recht elegant wenn man ne serielle Schnittstelle hat das man "ganz normal" mit der Software reden kann. Sprich das man Befehle im Klartext und Zahlen in einem lesbaren Format eintippern kann und eine SW-Blackbox kümmert sich um die Prüfung und Aufbereitung und gibt mir als Programmierer meiner Software dann noch nen Aufruf und die Daten mundgerecht vorgekaut. Das ganze benutze ich (mit den entsprechenden Verbesserungen im laufe der Zeit) schon sehr lange, und es läuft sehr stabil. Und bisher hab ich das auf mehreren Platformen portiert und funktionierte (fast) immer auf anhieb. Laufen tut das ganze auf AVR's (tlw schon mit den optimierungen das die Befehle nicht im RAM liegen sondern direkt ausm Flash "beackert" werden), nem PIC (dsPIC33), einem Prozessor von Renesas (nen 32-Bit Tierchen), und eben auch Windows. Nen STM32 sollte mal gar kein Problem sein. Und wenn du frei in der Wahl deines Formates bist kann ich dir nur ein echtes Text-Format empfehlen. Ist vllt nicht gerade Speicher bzw Performance optimiert, aber es ist immer schön wenn man die Befehle im Klartext lesen kann. Man erkennt Fehler eifnach etwas schneller als wenn man sich erst irgendwelche Binärdaten an der richtigen position im Buffer rausfischen muss, von Hex nach Dez wandeln muß um einen einzigen Wert zu überprüfen. Und Je nach Anwendung hat man noch ganz andere Daten bzw Aufbauten von Befehlen und dann ist man immer erstmal beschäftigt das ganze auseinanderzufummeln.
Hallo, ich kram den mal wieder vor :-) Ich suche sowas für nen 32-Bitter (XMC1300), hast du dafür (oder ähnlich Cortex-M) schonmal mwas gemacht? Ich finde deinen ansatz mit der möglichkeit, die einzelnen Wörter mehrfach zu verwenden und die Reinfolge ausschlaggebend zu machen, ideal. MFG Christoph
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.