www.mikrocontroller.net

Forum: Codesammlung uShell - ein universeller Parser für uCs


Autor: TheMason (Gast)
Datum:
Angehängte Dateien:

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
Autor: Läubi .. (laeubi) (Moderator) Benutzerseite
Datum:

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 ;)
Autor: TheMason (Gast)
Datum:

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

gruß
rene
Autor: Gast (Gast)
Datum:

ist "V" auch nicht von "VERSION" zu unterscheiden?
Autor: TheMason (Gast)
Datum:

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 :-)
Autor: TheMason (Gast)
Datum:

hat das jemand mal getestet und kann feedback geben ?

gruß
rene
Autor: TheMason (Gast)
Datum:

kein feedback ?! *grummel
Autor: Peter Dannegger (peda)
Datum:

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
Autor: TheMason (Gast)
Datum:

>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
Autor: Peter Dannegger (peda)
Datum:
Angehängte Dateien:

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
Autor: TheMason (Gast)
Datum:

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.
Autor: Simon K. (simon) Benutzerseite
Datum:

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
:-)
Autor: TheMason (Gast)
Datum:

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) },
  ...
};
Autor: Peter Dannegger (peda)
Datum:

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
Autor: TheMason (Gast)
Datum:

>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
Autor: TheMason (Gast)
Datum:
Angehängte Dateien:

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.
Autor: TheMason (Gast)
Datum:
Angehängte Dateien:

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)
Autor: Peter Dannegger (peda)
Datum:

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.
Autor: TheMason (Gast)
Datum:

>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.
Autor: TheMason (Gast)
Datum:

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.
Autor: Mark de Jong (Gast)
Datum:

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,
Autor: TheMason (Gast)
Datum:

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
Autor: TheMason (Gast)
Datum:
Angehängte Dateien:

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
Autor: Mark de Jong (Gast)
Datum:

Hallo TheMason,

Danke für die hilfe funktion.

Grüße Mark,
Autor: Mark de Jong (Gast)
Datum:

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,
Autor: TheMason (Gast)
Datum:

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.
Autor: Ha Jo (Gast)
Datum:

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
Autor: TheMason (Gast)
Datum:

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
Autor: Ha Jo (Gast)
Datum:

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
Autor: TheMason (Gast)
Datum:

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.
Autor: Ha Jo (Gast)
Datum:

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
Autor: TheMason (Gast)
Datum:

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
Autor: Ha Jo (Gast)
Datum:

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
Autor: TheMason (Gast)
Datum:

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
Autor: hans (Gast)
Datum:

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
Autor: TheMason (Gast)
Datum:

@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.
Autor: chris (Gast)
Datum:

Hallo, eine Frage,
ist eine Implementation mittels Hash von interesse, ohne Möglichkeit
einer  Hilfe.
Autor: TheMason (Gast)
Datum:

@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
Autor: chris (Gast)
Datum:

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')
Autor: TheMason (Gast)
Datum:

@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 ?
Autor: chris (Gast)
Datum:

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)
Autor: chris (Gast)
Datum:

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.
Autor: Georg (Gast)
Datum:

@TheMason (Gast)

Könntest du bitte mal Groß- und Kleinschreibung benutzen? Deine Beiträge
sind eine Zumutung!
Autor: Marko (Gast)
Datum:
Angehängte Dateien:

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.
Autor: Simon K. (simon) Benutzerseite
Datum:

> command_t mycmds
und die Strings könnten noch ins ROM statt ins RAM ;)
Autor: Marko (Gast)
Datum:

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:
import sys

def printhelp(s):
  """Print a list of available commands."""
  cmds = commands.keys()
  cmds.sort()
  for c in cmds:
    print c + '\t' + commands[c].__doc__

def exit(s):
  """Exit this shell."""
  print 'closing connection'
  msp = ''
  raise SystemExit

def test(s):
  """Print arguments."""
  if len(s) > 1:
    print "there were " + str(len(s)) + " arguments"
  else:
    print "there was 1 argument"

  print s

commands = {'?':printhelp,
      'x':exit,
      'test':test
}

while 1:
  sys.stdout.write('>> ')
  s = sys.stdin.readline()
  if s.split() == []: continue
  if s.split()[0] not in commands:
    print 'invalid command'
    continue
  commands[s.split()[0]](s.split())

Das Prinzip ist das gleiche.
Autor: Simon K. (simon) Benutzerseite
Datum:

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
Autor: Marko (Gast)
Datum:

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

Sehr gut beobachtet. Du mußt Dipl.-Inf. sein ... ;)
Autor: TheMason (Gast)
Datum:

@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.
Autor: chris (Gast)
Datum:

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.
Autor: eku (Gast)
Datum:

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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel




Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder GIF-Format hochladen.
Siehe Bildformate
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken erkennst du die Nutzungsbedingungen an.

webmaster@mikrocontroller.netImpressumNutzungsbedingungenWerbung auf Mikrocontroller.net