Forum: Projekte & Code uShell - ein universeller Parser für uCs


von TheMason (Gast)


Angehängte Dateien:

Lesenswert?

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

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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 ;)

von TheMason (Gast)


Lesenswert?

machbar ists bestimmt. vielleicht in einer späteren version.
erstmal schauen wer diese version gebrauchen kann :-)

gruß
rene

von Gast (Gast)


Lesenswert?

ist "V" auch nicht von "VERSION" zu unterscheiden?

von TheMason (Gast)


Lesenswert?

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 :-)

von TheMason (Gast)


Lesenswert?

hat das jemand mal getestet und kann feedback geben ?

gruß
rene

von TheMason (Gast)


Lesenswert?

kein feedback ?! *grummel

von Peter D. (peda)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

>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

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

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

von TheMason (Gast)


Lesenswert?

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.

von Simon K. (simon) Benutzerseite


Lesenswert?

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 
:-)

von TheMason (Gast)


Lesenswert?

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) },
  ...
};

von Peter D. (peda)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

>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

von TheMason (Gast)


Angehängte Dateien:

Lesenswert?

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.

von TheMason (Gast)


Angehängte Dateien:

Lesenswert?

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)

von Peter D. (peda)


Lesenswert?

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.

von TheMason (Gast)


Lesenswert?

>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.

von TheMason (Gast)


Lesenswert?

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.

von Mark de Jong (Gast)


Lesenswert?

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,

von TheMason (Gast)


Lesenswert?

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

von TheMason (Gast)


Angehängte Dateien:

Lesenswert?

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

von Mark de Jong (Gast)


Lesenswert?

Hallo TheMason,

Danke für die hilfe funktion.

Grüße Mark,

von Mark de Jong (Gast)


Lesenswert?

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,

von TheMason (Gast)


Lesenswert?

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.

von Ha Jo (Gast)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

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

von Ha Jo (Gast)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

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.

von Ha Jo (Gast)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

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

von Ha Jo (Gast)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

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

von hans (Gast)


Lesenswert?

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

von TheMason (Gast)


Lesenswert?

@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.

von chris (Gast)


Lesenswert?

Hallo, eine Frage,
ist eine Implementation mittels Hash von interesse, ohne Möglichkeit 
einer  Hilfe.

von TheMason (Gast)


Lesenswert?

@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

von chris (Gast)


Lesenswert?

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')

von TheMason (Gast)


Lesenswert?

@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 ?

von chris (Gast)


Lesenswert?

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)

von chris (Gast)


Lesenswert?

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.

von Georg (Gast)


Lesenswert?

@TheMason (Gast)

Könntest du bitte mal Groß- und Kleinschreibung benutzen? Deine Beiträge 
sind eine Zumutung!

von Marko (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Simon K. (simon) Benutzerseite


Lesenswert?

> command_t mycmds
und die Strings könnten noch ins ROM statt ins RAM ;)

von Marko (Gast)


Lesenswert?

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.

von Simon K. (simon) Benutzerseite


Lesenswert?

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

von Marko (Gast)


Lesenswert?

> Das braucht aber sicher mehr als 80 Byte RAM :D

Sehr gut beobachtet. Du mußt Dipl.-Inf. sein ... ;)

von TheMason (Gast)


Lesenswert?

@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.

von chris (Gast)


Lesenswert?

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.

von eku (Gast)


Lesenswert?

Dann hänge den Source doch bitte hier ins Forum.

von Timo (Gast)


Lesenswert?

Gibts inzwischen eine neue version ?

ich suche sowas fuer den STM32

von Rene B. (themason) Benutzerseite


Lesenswert?

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.

von Timo (Gast)


Lesenswert?

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.

von Rene B. (themason) Benutzerseite


Lesenswert?

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 ?

von Timo (Gast)


Lesenswert?

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

von Rene B. (themason) Benutzerseite


Lesenswert?

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.

von Christoph (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.