Hallo, ich beschäftige mich gerade mit der Thematik, Befehle an einen µC zu senden. Im kleinen Rahmen konnte ich mir bisher immer damit behelfen, einzelnen Zeichen bestimmte Funktionen zuzuordnen. Das aktuelle Projekt ist ein bisschen größer (geworden als ich dachte), und da wäre eine Befehlsverarbeitung mit Parametern geschickter. Je mehr ich nun versuche, in die Thematik zu kommen, desto verwirrender wird's momentan: suche ich nun einen Interpreter, einen Parser, eine Commandshell oder einen Mix aus allem? Soweit ich es verstanden habe, sind diese Begriffe nicht unbedingt Synonyme für ein und dasselbe. Von der Art her hätte ich's gern ála Kommandokonsole. Was mir vorschwebt, wäre z.B. Unterstützung von Umgebungsvariablen, die man auch als Parameter an Kommandos weitergeben kann, und die Möglichkeit, Befehle bzw. mathematische Ausdrücke/Formeln, die einen Rückgabewert liefern, selbst als Parameter übergeben zu können. Was ich also zumindest schon mal ausklammern kann: ich will (vermutlich) keinen (vollwertigen) Compiler o.ä. basteln. Von den verschiedenen Parsertypen, die ich bis jetzt gesehen habe, wäre es nach meinem Verständnis wohl am ehesten ein mathematischer Parser. Also "Kommandoformel" eintippen, Enter, fertig (oder Fehlermeldung grins ). Ist jetzt natürlich sehr vereinfacht ausgedrückt, das ist mir klar. Laufen soll das Ganze auf einem Cortex-M0 bzw. M3 (bis 64kB RAM), programmiert wird in C. Ich habe mir einige hier im Forum verfügbare Implementationen verschiedener Parser heruntergeladen und bin gerade dabei, die jeweilige Funktionsweise durchzugehen. Das ist zwar hilfreich, um die grobe Funktionsweise an sich zu verstehen, aber ich bin jetzt an einem Punkt angekommen, an dem ich mich frage, ob ich den Lexer/Parser selber schreibe oder zu Tools wie Flex/Bison bzw. GOLD greife. Selber schreiben würde natürlich die Details auftun und dem Verständnis sehr helfen. Die Verwendung von Tools ist gefühlt nicht unbedingt schneller, wenn ich davon ausgehe, dass es ein paar Anläufe braucht bis es korrekt beschrieben ist und funktioniert. Zumal es bei den Tools wohl so ist, dass generierte Ausgabe evtl. immer angepasst werden muss (Tabellen ins Flash, etc.). Zu was würdet ihr tendieren, selber schreiben oder Parsergeneratoren verwenden? Und im Fall von selber schreiben, was nehmt ihr als Basis? Es gibt zig Webseiten, welche die Funktionsweise von Lexer/Parser recht gut, aber für mich abstrakt, erklären. Ich konnte mit meinen Suchbegriffen nichts finden, was ich als Grundlage verwenden konnte, um damit von Grund auf einen Parser zu schreiben. Was brauche ich denn alles dazu? * die Befehlszeile muss in Teilstrings zerlegt werden, Trenner sind ',' sowie Leerzeichen und Tabs (es sei denn, es kommt ein Paar Anführungszeichen vor) * die Teilstrings werden typisiert, also in entsprechende Datentypen/Operatoren/Funktionsaufrufe konvertiert und/oder direkt als String/Bezeichner zusammen mit einem entsprechenden Tokenwert weiter gereicht * Aus den Token wird der ParseTree aufgebaut und entsprechend abgearbeitet Das Zerlegen, Konvertieren und die Werte mit Token zusammenführen traue ich mir noch zu (das dynamische Speicherhandling muss ich mir noch anschauen), aber das Erstellen und Abarbeiten vom ParseTree ist mir noch nicht klar. Habt ihr Tipps für mich? Ralf
Hängt davon ab wie komplex die Sprache ist. Wenn es eine reguläre Sprache ist, reichen reguläre Ausdrücke und du kannst dir einen richtigen Parser sparen. Das sollte für die meisten einfachen Batch/Shell-Sprachen ausreichen. Wenn du so etwas wie mathematische Formeln mit beliebiger Verschachtelung lesen können willst, brauchst du doch einen Parser. Schau dir auch mal Boost.Spirit an, damit brauchst du keinen Code zu generieren sondern kannst die Grammatik direkt im C++ Code definieren, aus welcher dann durch den C++ Compiler ein optimierter Parsing-Algorithmus generiert wird. Du musst dir halt überlegen, ob der Parserbaum (und der ganze Overhead für die Speicherverwaltung) in 64kB RAM passt...
Was willst Du denn genau machen? Ich bin gerade dabei eine Wetterstation zu bauen und da habe ich ein ähnlich gelagertes Problem. Ich habe einen "Hauptrechner" der die Daten von mehreren Clients konsolidiert und weitere Berechnungen durchführt. Die Clients sind untereinander und mit dem Hauptrechner per RS232, also seriell, verbunden. Alle Komponenten bilden also eine Kette (Hauptrechner <-> client1 <-> Client2 usw.) Für Kommunikation habe ich mir folgenden Stringaufbau ausgedacht: <Gerätegruppe>ID|Art|Param</Gerätegruppe>. Die gruppiert mehrere Clienten zu einer Gruppe. ID ist eine eindeutige ID für den jeweiligen Clienten innerhalb der Gruppe. Art gibt an ob Daten (D) oder Kommandos (C) übertragen werden. Param ist eine Parameterliste wobei die Parameter mit Semikola getrennt werden. Bei der Art Kommando (C) ist der erste Parameter das Kommando/der Befehl selbst. Ich habe mir eine kleine Routine geschrieben die diesen String auseinander fummelt. Der Client erkennt an Hand der Gruppe und der ID ob der Befehl für ihn selbst bestimmt ist, wenn nein wird der komplette String über den 2. UART an den nächsten Clienten durchgereicht. Letztendlich läuft das Ganze auf einen Parser hinaus. Die Kommandos auf die der Client reagiert sind in einer Liste hinterlegt. Wenn ein Client eine Anforderung an den Hauptrechner sendet, dann bekommt dieser ebenfalls obigen String. Er erkennt welcher Client eine Anforderung gesendet hat und baut mit Daten (Gruppe und ID) einen Antwortstring. Die Kommandos, die die Clienten verstehen sind können für jeden unterschiedlich sein. Bei mir funktioniert das für den vorgesehenen Zweck ganz gut. Vielleicht hilft es Dir ja etwas weiter. Das Ganze erhebt keinen Anspruch auf Perfektion . Kann man sicher auch anders lösen.
Moin, für Befehle mit einfachen Argumenten hat sich bei mir folgendes bewährt, eine Art RPN-Konstrukt: Sendet man dem "Parser" einen String, sucht er in einer Liste nach bekannten Befehlen (zB set_samplingrate). Wird kein bekannter Befehl gefunden, wird versucht, den string als Zahl zu interpretieren und die Zahl wird auf einen stack gepackt. Ist der Befehl bekannt, wird dieser ausgeführt und kann auch Werte vom stack nehmen und auch Werte wieder auf diesen schieben. Eine Konfig würde dann zB so aussehen: 1000 set_samplingrate 24 set_res 0 20 23 set_clock Nicht sehr elegant, kein richtiger Parser, aber fix programmiert und erfüllt seinen Zweck (bei mir^^). Ist natürlich dann auch nicht in der art samplingrate=1000... Viele Grüße Wilko
:
Bearbeitet durch User
Ralf schrieb: > Selber schreiben würde natürlich die Details auftun > und dem Verständnis sehr helfen. Die Verwendung von > Tools ist gefühlt nicht unbedingt schneller, wenn ich > davon ausgehe, dass es ein paar Anläufe braucht bis > es korrekt beschrieben ist und funktioniert. Zumal es > bei den Tools wohl so ist, dass generierte Ausgabe evtl. > immer angepasst werden muss (Tabellen ins Flash, etc.). > > Zu was würdet ihr tendieren, selber schreiben oder > Parsergeneratoren verwenden? Und im Fall von selber > schreiben, was nehmt ihr als Basis? Man kann mit endlichen Automaten ("finite state machines") recht weit kommen. Voraussetzung ist natürlich, dass man die Grundbegriffe zuverlässig beherrscht (Festlegen der Zustände; Überführungsfunktion; Ausgabefunktion) und eine gewisse Systematik hat, wie man das aufschreibt. Kenntnisse der Steuerungstechnik sind hilfreich. Je nach Problem kann auch die umgekehrte polnische Notation ein Geheimtipp sein; der zugehörige Kellerautomat ist noch ziemlich einfach, aber schon ziemlich leistungsfähig.
Wilko N. schrieb: > ine Art RPN-Konstrukt: Sendet man dem "Parser" einen String, sucht er > in einer Liste nach bekannten Befehlen (zB set_samplingrate). > Wird kein bekannter Befehl gefunden, wird versucht, den string als Zahl > zu interpretieren und die Zahl wird auf einen stack gepackt. > Ist der Befehl bekannt, wird dieser ausgeführt und kann auch Werte vom > stack nehmen und auch Werte wieder auf diesen schieben. Client stürzt ab und es liegen noch Werte auf dem Stack, was passiert mit den Werten auf dem Stack?
Wenn ich Dein Problem richtig verstanden habe, ist TCL fuer dieses Problem gemacht. Du kannst es in C einbetten, und eigene Kommandos definieren. Nur mal so.als.Alternativvorschlag.
Ralf schrieb: > Je mehr ich nun versuche, in die Thematik zu kommen, desto verwirrender > wird's momentan: suche ich nun einen Interpreter, einen Parser, eine > Commandshell oder einen Mix aus allem? Die meisten einfachen Commandshells sind als Interpreter realisiert. Es gibt allerdings kein Gesetz, dass das vorschreibt. Ein Parser bereitet Eingangsdaten so auf, dass man deren Bedeutung analysieren kann. Sowohl Compiler als auch Interpreter verwenden daher Parser. Bei einfachen Sprachen geht der Parser im Code schon mal unter, weil er ad-hock im Code eines Interpreters (seltener eines Compilers) zusammengeschustert wurde. Mixe von Compiler und Interpreter gibt es schon lange. Traditionell wird dabei in einen Zwischencode (statt Maschinencode) compiliert, der zur Laufzeit interpretiert wird. In neuerer Zeit compiliert man den Zwischencode teilweise zur Laufzeit nach (Hotspot Compiler). Die älteste für Embedded genutzte Sprache, die ein Mix ist, dürfte FORTH sein. Forth kommt mit einem eingebauten Kommandozeilen-Interpreter, einem Compiler, und einem weiteren Interpreter[1] für den kompilierten Zwischencode. Das ganze passt trotzdem noch in ein paar Kilobyte. Eine andere Sprache, die typischerweise als Mix aus Kommandozeile und Programmierumgebung kommt und traditionell als Interpreter, ist BASIC. Auch hier passen klassische Implementierungen in ein paar Kilobyte[2]. Beispiel https://en.wikipedia.org/wiki/Tiny_BASIC Nicht zu vergessen die original v7 Bourne-Shell ist vom Code auch ziemlich klein. Allerdings verwendet sie viele Betriebssystemfunktionen, die dir fehlen dürften. > Von der Art her hätte ich's gern ála Kommandokonsole. Nur die Kommandozeile, oder auch (wie bei FORTH, BASIC, sh) die Möglichkeit kleine Programme zu erstellen? > vorschwebt, wäre z.B. Unterstützung von Umgebungsvariablen, die man auch > als Parameter an Kommandos weitergeben kann, und die Möglichkeit, > Befehle bzw. mathematische Ausdrücke/Formeln, die einen Rückgabewert > liefern, selbst als Parameter übergeben zu können. Wenn du die Formeln in bekannter Notation schreiben möchtest, dann eher einen BASICaritigen Interpreter. Wenn RPN ok ist, dann FORTH. > Von den verschiedenen Parsertypen, die ich bis jetzt gesehen habe, wäre > es nach meinem Verständnis wohl am ehesten ein mathematischer Parser. Der dir alleine nichts nützt. Du brauchst einen Kommandozeilen-Interpreter, der natürlich intern einen Parser verwendet. Bei einem Mathe-Parser fehlt der Interpreter und was willst du mit etwas, dass einzig und alleine Formeln zur weiteren Bedeutungsanalyse auswertet? > Ich habe mir einige hier im Forum verfügbare Implementationen > verschiedener Parser heruntergeladen Ein Parser alleine hilft nicht. Ich hoffe mal, dass ein paar davon Interpreter waren, die nur jemand Parser genannt hat. > aber ich bin jetzt an einem Punkt angekommen, an dem ich mich > frage, ob ich den Lexer/Parser selber schreibe oder zu Tools wie > Flex/Bison bzw. GOLD greife. Wenn du eine fertige Sprache oder einen fertigen Interpreter nimmst, dann hat dir jemand sowohl diese Entscheidung, als auch die Implementierung des Parsers abgenommen. > Zu was würdet ihr tendieren, selber schreiben oder Parsergeneratoren > verwenden? Bei kleinen Aufgaben Interpreter mit Parser von Hand schreiben Bei großen Aufgaben fertige Sprache nehmen. Parsergenerator nur wenn es um eine große, spezielle Aufgaben geht, für die es nichts gibt. Für die Auswertung von Kommandozeilen gibt es mehr als genug. > Ich konnte mit meinen > Suchbegriffen nichts finden, was ich als Grundlage verwenden konnte, um > damit von Grund auf einen Parser zu schreiben. Dragon Book oder Drachenbuch googlen. > aber das Erstellen und Abarbeiten vom ParseTree ist mir noch > nicht klar. Das ist der Kern des Interpreters oder Compilers. > Habt ihr Tipps für mich? Dragon Book / Drachenbuch oder eine fertige Sprache nehmen. ----- [1] Beide Interpreter teilen sich den selben Code, der zweite kostet praktisch nichts extra. [2] Schon frühe BASIC-Interpreter hatten als Optimierung gelegentlich eine kleine Compiler-Komponente. Die Zeilennummer von Sprungzielen wurde in eine Speicheradresse übersetzt. Damit sparte man sich das elendig langsame lineare Suchen nach der Sprungziel-Zeilennummer bei jedem Sprung.
Ralf schrieb: > Befehle an einen µC zu > senden. Im kleinen Rahmen konnte ich mir bisher immer damit behelfen, > einzelnen Zeichen bestimmte Funktionen zuzuordnen. Das aktuelle Projekt > ist ein bisschen größe Die Tipps für parsen und interpretieren hier sind schön und gut aber denk mal nach ob es wirklich die richtige Lösung ist. Eine µC seitige API ist imho die beste Lösung und auch für sehr große Anwendungen geeignet. In C kannst du für jeden Befehl eine struct mit den Parametern bauen. Über deine Verbindung (RS232, RS485, USB, TCP/IP, ...) schickst du dann [START, FUNKTIONSID, LÄNGE, PAYLOADSTRUCT, ... PRÜFSUMME, STOP]. Im µC reicht dann ein einfaches switch(FUNKTIONSID) {...} et voila. Das hat mMn gleich mehrere Vorteile: * auf PC Seite kannst du eine graphische Oberfläche oder einen einfachen Kommandointerpreter bauen. (z.B. mit QT oder TCL/TK sogar portabel für gängige OS) * bei der Übertragungsschicht bist du flexibel (s.o) * auf µC Seite sparst du dir das rumgefrickel mit Strings, parsen etc. Wenn du keine "Programme" zum interpretieren an den µC senden musst, ist diese Lösung besonders für große Projekte aus meiner Sicht wesentlich sauberer und auch flexibler. Gruß Mike
Hallo zusammen, vielen Dank für die zahlreichen Antworten. @Dr.Sommer: > Du musst dir halt überlegen, ob der Parserbaum (und der ganze Overhead für > die Speicherverwaltung) in 64kB RAM passt... Kann das wirklich wirklich 64kB sprengen? Mir ist bewusst, dass es mit ein paar Bytes RAM nicht getan ist, aber das so etwas tatsächlich an die 64 kB oder darüber kommen kann, hätte ich nicht gedacht. @Zeno: > Was willst Du denn genau machen? Danke für die Erläuterungen zu deiner Wetterstation. Später möchte ich auch mal etwas mit mehreren Teilnehmern an einem Bus machen, da eignet sich der (relativ) fixe Aufbau wie es dein Protokoll macht wahrscheinlich eher. In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben. @Andreas Rückert: Danke, schau ich mir an. @ Detlev T.: Schaue ich mir ebenfalls an. @Wilko: Mjaaaa, das geht ungefähr in meine Wunschrichtung, wobei ich schon gern wie du auch geschrieben hast, die Zuweisung mit '=' und den Funktionsaufruf wahlweise mit Klammernpaaren und Parameter getrennt durch Komma sowie ohne Klammernpaar und Komma hätte - in dem Fall evtl. benannte Parameter, damit die Reihenfolge keine Rolle spielt. Wobei das jetzt schon wieder arg oversized klingt. Das ging mir halt anfangs so durch den Kopf. @Possetitjel: Das steht auch auf der Liste. Intern in UPN umzubauen sollte auch gehen. @Dumdi Dum: Okay, dann werde ich mir TCL auch ansehen =) @Hannes Jaeger: Vielen Dank für die ausführlichen Erläuterungen. >> Von der Art her hätte ich's gern ála Kommandokonsole. > Nur die Kommandozeile, oder auch (wie bei FORTH, BASIC, sh) die > Möglichkeit kleine Programme zu erstellen? Naja, wenn FORTH insgesamt nicht wirklich groß (relativ zu anderen Interpretern/Parsern) ist, könnte es auch die "Vollversion" sein. Ich kann mich ja trotzdem erstmal auf die Eingabe einzelner Zeilen beschränken. > Der dir alleine nichts nützt. Du brauchst einen Kommandozeilen- > Interpreter, der natürlich intern einen Parser verwendet. > Bei einem Mathe-Parser fehlt der Interpreter und was willst du mit > etwas, dass einzig und alleine Formeln zur weiteren Bedeutungsanalyse > auswertet? > Ein Parser alleine hilft nicht. Ich hoffe mal, dass ein paar davon > Interpreter waren, die nur jemand Parser genannt hat. Genau deswegen habe ich eingangs geschrieben, dass ich nicht alle Begrifflichkeiten für ein und dasselbe halte ;) >> Ich konnte mit meinen Suchbegriffen nichts finden, was ich als Grundlage >> verwenden konnte, um damit von Grund auf einen Parser zu schreiben. > Dragon Book oder Drachenbuch googlen. Danke, werd ich machen. @MikeH: Danke für die Idee. Ich sehe die Vorteile, aber der Nachteil ist m.E. dass man auf der Gegenseite die Befehle "generieren" muss. Das wollte ich eigentlich vermeiden, an zwei Ecken zu programmieren. Ich muss mir das nochmal durch den Kopf gehen lassen, ob das nicht doch evtl. die bessere Wahl ist. Ralf
Ralf schrieb: > Kann das wirklich wirklich 64kB sprengen? Das hängt von der Größe der Eingabe ab! Ein Quellcode mit tausenden Zeilen wird da wohl nicht reinpassen, eine einzelne Befehlszeile mit Max. 100 Syntax Elementen schon eher. Es wäre dabei schlau beim parsen auf "out of memory " zu prüfen und dann sauber abzubrechen anstatt abzustürzen ;-) Schau vielleicht auch mal nach eLua das ist für sowas gemacht. Ich finde es auf jeden Fall spannend so etwas selber zu implementieren auch wenn das länger als nötig dauert, du musst selbst wissen wie viel du selber machen willst. Man kann ja auch seinen eigenen Parser bauen, dem man Grammatik + Eingabetext gibt und der den Syntax Baum zurückgibt, ggf direkt ohne einen Parser-Code (in C) zu generieren. Die Grundversion ist relativ simpel, aber das effizient und flexibel hinzubekommen ist nicht so einfach ;-)
wie wäre es mit JSON? Es gibt einen sehr kompakten parser, JSMN : http://zserge.com/jsmn.html Ist zwar kein richtiger Kommonadozeileninterpreter, aber das lässt sich dann gut durch ein Programm bedienen.
Ralf schrieb: > In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur > Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine > oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben. Hier reicht es doch aus, dass ein festes Protokoll verwendet wird. Beispielsweise ein Befehlswort aus einem oder mehreren Zeichen, gefolgt von irgendwie getrennten Daten. Die Daten könnten auch binär übertragen werden. Das macht es zwar einem Menschen schwerer, per Terminal zuzugreifen, ist aber einfacher zu parsen (zB. Zahleneingaben). Beispie:
1 | D:100 Setze Verzögerung auf 100. |
2 | D Liefert die aktuell eingestellte Verzögerung zurück. |
3 | X:2,3,4 Befehl mit mehreren Parametern. |
Hierzu ist kein flexibler Parser notwendig. Es müssen lediglich die Parameter aufgeschlüsselt werden. Ist jeweils nur maximal ein Parameter erlaubt, macht es die Sache noch einfacher.
Bison, JSON, Boost, TCL...und das auf einem uC? Im Ernst, Leute? Ich schmeiss gleich noch .NET in die Runde. Mein Credo, keep it simple. Eine shell, die keine mathematischen Ausdrücke auflösen muss, braucht keinen dicken Parser, es reicht eine einfache State-Machine mit weniger Zeilen, als ihr euch hier schon einen Wolf geschrieben habt. Als Anschauungsbeispiel (wenn zu primitiv, darf es gerne aufgebohrt werden): http://section5.ch/dsp/blackfin/shell-1.1eval.tgz, siehe shell.c Wenn die Shell zum KOnfigurieren/Abfragen von Werten im Klartext dienen soll: Die guten alten AT-Modem-Kommandokonzepte sind zwar grottig, aber nicht totzukriegen. Einfacher ist nur noch eine simple register-orientierte API.
Ralf schrieb: > In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur > Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine > oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben. Dann reicht ein relativ simpler Parser, den du z.B. mit bison generieren lassen kannst. In das action-Feld jeder Parser-Regel packst du einfach Aufrufe der Funktionen, die die gewünschte Operation ausführen. Du ersetzt einfach deine bisherige switch () case Orgie durch den Parser. Ob du für den Lexer auf z.B. flex zurückgreifen mußt oder den von Hand schreiben willst, kommt auf die Komplexität deiner "Sprache" an.
Die "Klassiker" der einfachen Kommandointerpreter nehmen einfach die ersten N (z.B. 4) Zeichen und interpretieren sie als Zahl die dann einfach in einem Switch landet. Siehe z.B. die ganzen ftpd. Abkuerzungen waeren dann nur ein weiterer case in diesem Switch. Fuer Trennzeichen, Zahlen und Stringargumente braeuchte Mann dann noch weitere Funktionen die Pattern Matching und Evaluierung betreiben. Ob sich fuer sowas ein *lex" lohnt? Sowas wie: > 1000 > set_samplingrate > 24 > set_res > 0 > 20 > 23 > set_clock koennte auch ein Forthinterpreter relativ direkt verwursten.
Ralf schrieb: > In meinem konkreten Fall möchte ich (erstmal) bspw. Parameter zur > Laufzeit ändern/abfragen, Ein-/Ausgänge lesen/setzen und ggf. die eine > oder andere Funktion aufrufen. Also erstmal unterste Steuerungsaufgaben. Das würde mit meinem Konstrukt schon gehen. Bei meiner Lösung würde ich im Parameterblock z.B. Folgendes schreiben: setPort;4;5;OUTPUT;1 >> übersetzt: setze Port 4.5 als Ausgang und auf HIGH. Du müßtest also dem Befehl "setPort" nur eine passende Funktion zuordnen und dieser die restlichen Parameter übergeben. Den Rest von meinem Ausgangskonstrukt kann man ja erst mal weglassen, wenn man keinen Bus aufbauen will.
> Habt ihr Tipps für mich?
Nimm Flex. Ich hab es schon genau fuer das was du da machen willst
verwendet. Im Prinzip brauchst du hoechstens 1-2h um dir das Inputscript
fuer flex zu schreiben das dir den C-Source erzeugt welche dann deine
Funktionen mit den richtigen Parametern aufrufen. Beim erstenmal
brauchst du vermutlich 3h laenger um die Manpage zu lesen oder etwas zu
lernen.
Der Vorteil von flex kommt danach. Wann immer du deinen Interpreter
spaeter um ein neues Kommando oder ein vorhandenes Kommando um einen
Parameter erweitern willst dann ist das in 1min zu loesen.
Ich hab ausserdem noch ein kleines Programm im Sourcetree welches eines
Testumgebung fuer mein Betriebssystem erzeugt. Damit kann ich
Aenderungen am Flexinterpreter auf dem PC testen bevor es in den
Microcontroller geht.
Wie schon gesagt, die Nutzung ist eine wahre Freude! Und weil es so
schoen schnell und einfach geht neigt man dazu jede Menge kleine Befehle
in seinen Interpreter einzubauen um die Funktionalitaet des eigenen
Programmes und seiner Hardware sehr gut testen zu koennen. Dadurch
steigt die Codestabilitaet immens an. Bei mir laeuft das fertige
Programm dann auf einem ST32F103.
Olaf
Vor acht Jahren hatte ich die Gelegenheit eine Zeitlang in der Arbeit mit einem FORTH System zu spielen welches auf einer Embedded Artist Dev Board lief mit LPC2467 Prozessor drauf und TFT LCD und in C geschrieben war. Es war sehr leicht neue Keywords ins Dictionary hinzuzufügen und man konnte alles von der command line aus machen. Auch als RPN Calculator konnte man es verwenden. Hat mächtig Spaß gemacht damit zu arbeiten. Neue Commands konnten praktisch in Minuten geschrieben und compiliert werden. Compiler war von IAR. Es war ein sehr beeindruckendes System. Ein Bekannter von mir schrieb das Ganze von Start an. Sogar 320x240 TFT Graphikfunktionion hatten wir laufen. Für das Testen von Hardware war FORTH ideal. Leider habe ich nicht mehr die Sourcen dafür. Aber es war eine gute Lern Experience. Eigentlich sollte man auch heutzutage noch FORTH nicht übersehen. Es könnte für bestimmte Sachen immer noch ganz nützlich sein. Früher hatte man damit angeblich sogar große Teleskope gesteuert.
:
Bearbeitet durch User
Ich hab mir in solchen Situationen immer meinen Parser mit strtok(), atoi(), strtol() und so weiter selbst zusammengeschustert. Erstes Wort ist der Befehl, dann kommen die Argumente, reicht aus. Die Hauptschleife testet die wichtigsten Befehle ("quit", "help") direkt ab, wandert dann durch eine Tabelle { "befehl", "beschreibung", funktionspointer }[NUM_COMMANDS] und ruft die entsprechende Funktion mit dem Reststring auf. Diese Funktionen parsen ihre Argumente selbst und geben einen Fehlercode zurück. Reicht völlig hin, ist mit 20 Zeilen Code erledigt, und bis auf fehlende History und mangelhaften Zeileneditor (nur Backspace) auch relativ bequem. Wichtig ist, dass eine überlange Zeile keinen Überlauf produziert.
Falls moeglich sollte man das Protokoll zustandsfrei schreiben. Denn was geschieht, wenn ein Befehl unterwegs verloren geht ? Er wird wiederholt, wie selten auch immer das geschieht. Es geschieht ... zB SetAusgang=high : wiederholung ist ok ToggleAusgang : wiederholung ist wahrscheinlich nicht ok IncrementPower : wiederholung ist nicht ok Power=200 : wiederholung ist ok Datenblock(200) : wiederholung ist ok NextDatenblock : wiederholung ist nicht ok Alles klar ?
Forth ist mal gar nicht so exotisch wie's klingt, wer kennt nicht Chuck Moores Forth Prozessoren (HW). Die J1 und andere Derivate sind mal richtig cool. Irgendwer hat halt mal Stackmaschinen als uncoole Hardware definiert, so existieren sie vor allem in Software. Für einen Parser dürfte bei einer SM allerdings der kompakteste Code bei rausschauen.
Ralf schrieb: > Tools wie Flex/Bison Wenn, dann würde ich eher zu byacc als bison tendieren, schon der embedded-freundlicheren Lizenz wegen. Ich habe vor Jahren mal (aus Spaß) einen BASIC-Interpreter für einen ATmega128 mit flex/byacc geschrieben. War nicht ganz leichtgewichtig, funktionierte aber durchaus brauchbar. Sollte also auf einem M0 kein Problem sein. Du schleppst zwar bisschen mehr Overhead als bei einer handgefeilten Lösung mit, aber dafür kannst du das Ding einfach als Werkzeug benutzen und dich auf dein eigentliches Problem konzentrieren.
Also laut Wikipedia steht der erzeugte Code von Bison nicht zwangsläufig unter der GPL, dies war aber in älteren Versionen wohl anders... https://en.wikipedia.org/wiki/GNU_bison#Licence_and_distribution_of_generated_code
Möglich. Bison ist aber ohnehin m. W. aufgeblasener als byacc, schon deshalb wäre es für einen schmalbrüstigen Controller nicht meine Wahl gewesen. (Natürlich musste ich, damit das auf einem AVR sinnvoll funktioniert, sowohl byacc als auch flex patchen, damit sie Flash-Konstanten für die vielen Tabellen erzeugen. Für den hier gewünschten Cortex-M sollte es aber aus der Dose raus mit beiden funktionieren.)
Olaf schrieb: > Der Vorteil von flex kommt danach. Wann immer du deinen Interpreter > spaeter um ein neues Kommando oder ein vorhandenes Kommando um einen > Parameter erweitern willst dann ist das in 1min zu loesen. In Boost.Spirit geht das sogar noch schneller - man passt die Grammatikdefinition direkt im C++ Code an, und ruft den C++ Compiler neu auf. Man braucht keine externen Tools und man kann problemlos beliebig viele verschiedene Sprachen parsen ohne am Buildsystem rumbasteln zu müssen. Einfache Sprachen (z.B. reguläre) kann man in 1 Zeile parsen (inkl. Definition!). Das ist das Schöne an einer Lösung, die auf Metaprogrammierung basiert. Ein sehr einfaches Beispiel findet sich hier: Beitrag "Re: Datenreihe neu Sortieren" Alles ist kompakt in einer kleinen Sourcedatei, es musst nur eben Boost installiert sein. Was man prüfen müsste ist ob man dessen dynamische Speicherallokation genug reduzieren kann damit es auf Mikrocontroller passt.
Dr. Sommer schrieb: > Alles ist kompakt in einer kleinen Sourcedatei, es musst nur eben Boost > installiert sein. > > Was man prüfen müsste ist ob man dessen dynamische Speicherallokation > genug reduzieren kann damit es auf Mikrocontroller passt. Was höchstwahrscheinlich bedeutet, dass man ein simples Problem erfolgreich durch ein höchst komplexes ersetzt hat ;)
> In Boost.Spirit geht das sogar noch schneller - man passt die > Grammatikdefinition direkt im C++ Code an, und ruft den C++ Compiler neu > auf. Wieso sollte das jemand interessieren? Auf Mikrocontrollern wird zu 99% C verwendet und flex liefert reinen C-Code der ohne Aenderung direkt fuer einen Microcontroller compiliert und verwendbar ist. Auch flex ist schon etwas Resourcenverschwendend was Flashverbrauch angeht, aber da man ja heute praktisch immer 64-128k oder mehr hat ist das nicht mehr so das Problem. Olaf
Olaf schrieb: > Auf Mikrocontrollern wird zu 99% C verwendet Interessante Aussage, wo ist deine Quelle? Das hat mich schon immer interessiert. Viele behaupten, es sind 50% Matlab. Jedenfalls kann man erwiesenermaßen sehr gut C++ auf Mikrocontrollern nutzen, und es bei Bedarf auch mischen. C++ Code kompiliert auch auf Mikrocontrollern ohne Änderung, man muss nur an den entscheidenden Stellen die richtigen Container-Typen & Allokatoren auswählen. Daher finde ich es durchaus interessant. Habe aber gerade keine Zeit es selbst vernünftig auszuprobieren.
> Interessante Aussage, wo ist deine Quelle? Mein Bauch. > Viele behaupten, es sind 50% Matlab. Nur bei den Ruebennasen frisch von der Uni. In der Praxis lernen die dann entweder was richtiges oder machen auf Projektleiterassistent. :-) > Jedenfalls kann man erwiesenermaßen sehr gut C++ auf Mikrocontrollern > nutzen, und es bei Bedarf auch mischen. Theoretisch, aber die Schnittmenge aus guten Embeddedprogrammierern und guten C++ Programmierern ist zu klein. Daher noch keine praxisrelevanz. Ausserdem hat der OP gleich im ersten Posting zu erkennen gegeben das er in C programmiert. > Habe aber gerade keine Zeit es selbst vernünftig auszuprobieren. Du bist faul und willst anderen deine Traeume aufzwingen? Olaf
Olaf schrieb: > Mein Bauch. Ah, die gekonnte Argumentationsart sich Zahlen auszudenken wie sie einem passen. Olaf schrieb: > Theoretisch, aber die Schnittmenge aus guten Embeddedprogrammierern und > guten C++ Programmierern ist zu klein. Ich würde fast behaupten, dass gute Embedded Programmierer keine größeren Probleme mit C++ haben sollten. Olaf schrieb: > Daher noch keine praxisrelevanz. C++ gibts ja erst seit 30 Jahren, dauert noch ein paar bis es von der Industrie angenommen wird? Klingt eher nach Unwillen (was der Bauer nicht kennt, ...). Olaf schrieb: > Du bist faul und willst anderen deine Traeume aufzwingen? Ich wollte lediglich auf eine Möglichkeit hinweisen. Der OP darf ja auch selbst etwas tun. Du hast mit rumstänkern angefangen.
Hallo, sorry für die späte Rückmeldung, vielen Dank für eure weiteren Beiträge. Da ist ja ganz schön was zusammen gekommen =) @Dr.Sommer: Japp, sind ja jeweils nur einzelne Zeilen. Und out-of-memory versuche ich natürlich im Auge zu behalten. Bin auch grad am schauen, ob man Infos zum Heap oder "unbereinigte" malloc()-Aufrufe erkennen/abfragen kann. eLua geht nicht, das sprengt Flash & RAM, wenn ich die Infos auf der eLua-Seite so anschaue. @Johannes: JSON schau ich mir an, danke. @Brummbär: Ja, für das aktuelle Projekt würde das wahrscheinlich auch reichen. Parsen, Kommandozeile, etc. ist halt immer interessanter geworden, je mehr ich mich damit beschäftigt habe =) @Martin S.: grins .NET geht leider auch nicht auf dem System (aber jucken würd's mich schon, das mal auf einem µC zu probieren). Aber ich glaub da war auch noch was mit Lizenzen, etc. Das Beispiel schau ich mir an, danke. @Axel: Für lex & yacc hab ich mir ein Buch vom Kollegen geliehen, werd ich auch anlesen. @(º°): Stimmt, auch eine KISS-Variante. @Zeno: Das geht schon in die (meine) richtige Richtung. @Olaf: Hört sich gut an. Werd mich dazu durch das o.e. Buch lesen. @Gerhard: Hab ein bisschen bzgl. FORTH nachgeschaut, scheint wirklich für den Einsatz auf µCs gemacht zu sein (zumindest laut Wiki). @S.R.: Das war auch was, was mir durch den Kopf ging. Aber ist es nicht umständlich, wenn die Funktionen selbst parsen müssen? Das bläht doch immens auf, oder? @Sapperlot W: Ähm... nein, nicht ganz klar =) Was heisst zustandsfrei? Wenn ein Befehl nicht korrekt empfangen wird, dann muss erkannt werden, dass ein neuer Befehl beginnt anstatt diesen als Teil des "kaputten" Befehls zu erkennen? @Strubi: Du meinst, weil in FORTH die Eingabe gleichzeitig der Befehl selbst ist? @Jörg: Wie bereits erwähnt, sobald ich verstanden habe, wie Bison/YACC/BIYACC/FLEX/LEX etc. genau funktionieren, werde ich das mal anprobieren. @lazycoder: Bitlash muss doch in Verbindung mit der Arduino-Umgebung laufen, oder? Bei den restlichen Beiträgen ging es nicht konkret um mein Thema, daher antworte ich darauf nicht. Trotzdem interessant zu lesen ;) Nochmals danke an alle für die zahlreichen Antworten. Ralf
Ralf schrieb: > Hab ein bisschen bzgl. FORTH nachgeschaut, scheint wirklich für den > Einsatz auf µCs gemacht zu sein Sagen wir mal so: als es erfunden wurden ist, gab es noch gar keine Mikrocontroller oder man hat sie zumindest noch nicht so genannt. :) Aber für Mikrocomputer ist es in der Tat ausgelegt worden.
> Sagen wir mal so: als es erfunden wurden ist, gab es noch gar keine > Mikrocontroller oder man hat sie zumindest noch nicht so genannt. :) Forth war interessant als es noch nicht heutige Entwicklungssysteme gab. Als man also ein EPROM und etwas RAM am Microcontroller hatte und die Alternative aus EPROM loeschen/brennen bestand. Ich hab mich damals ja auch dafuer interessiert und st6forth geschrieben. Allerdings schon kein klassisches Forth sondern ein Compiler. Interessant waren die echten Forthinterpreter damals weil man in Echtzeit entwickeln konnte. Allerdings heute mit den Moeglichkeiten eines Debuggers und der Programmierschnittstelle auf der Zielhardware sehe ich keine grosse Relevanz mehr. Die Vorteile sind gering geworden, wenn zum Teil auch noch vorhanden. Dafuer sind aber die Nachteile extrem gewachsen. Ein Forth-programm ist nicht gerade ein Muster an selbsterklaerendem. Wenn man bedenkt was heutige Microcontroller an Platz bieten und das es Programme gibt die den auch nutzen dann kommt man sehr schnell an Grenzen wo man nicht mehr versteht was man selber geschrieben hat. Und diese Grenze ueberschreitet man deutlich schneller als bei C. Die Sprache ist aber natuerlich ein schoenes intellektuelles Spielzeug. So gesehen kann sich die Beschaeftigung doch wieder lohnen. :-) Olaf
Dr. Sommer schrieb: > C++ gibts ja erst seit 30 Jahren, dauert noch ein paar bis es von der > Industrie angenommen wird? Das ist doch kein Argument. Lisp gibt es seit 60 Jahren und es ist noch nicht auf muc angekommen. Das Problem ist, das C++ fuer Systeme mit Beschraenkungen bei der dynamischen Speicherverwaltung nur eingeschraenkt verwendbar ist und seine Staerken nicht ausspielen kann.
Mit dem innovativen Entwicklungssystem "Arduino" ist C++ aber schon seit 10 Jahren millionenfach auf dem Mikrocontroller angekommen. Nur bei manchem MC-Netz Nutzer geht's halt ein wenig länger.
Dumdi D. schrieb: > Das Problem ist, das C++ fuer Systeme mit Beschraenkungen bei der > dynamischen Speicherverwaltung nur eingeschraenkt verwendbar ist und > seine Staerken nicht ausspielen kann. C++ hat noch viel mehr Stärken außer ein paar Containern in der Standard Library, welche dynamische Speicherverwaltung brauchen. Das geht von sowas einfachem wir Funktions Overloads bis zu Metaprogrammierung. Und das kann man sehr wohl sinnvoll auf Mikrocontrollern nutzen. Der Hauptgrund warum C da noch so dominant ist, ist wahrscheinlich dass Embedded Entwicklung oft von E-Technikern gemacht wird und die nur C in Studium lernen. Im Informatik Studium hatten wir jedenfalls "Embedded C++".
Als konkretes Beispiel hier das Chameleon-Mini, was ich damals bei Kasper und Oswald entwickelt habe. Da gibt es eine einfache Kommandozeile. https://github.com/emsec/ChameleonMini/tree/master/Firmware/Chameleon-Mini/Terminal
Man kann sich natürlich auch einfach einen Tokenizer schreiben (oder strtok nehmen) und dann mit einem endlichen Automaten arbeiten. So habe ich schon AT-Parser geschrieben, als man µCs noch mit Dampf betrieben hat.
Dr. Sommer schrieb: > Dumdi D. schrieb: >> Das Problem ist, das C++ fuer Systeme mit Beschraenkungen bei der >> dynamischen Speicherverwaltung nur eingeschraenkt verwendbar ist und >> seine Staerken nicht ausspielen kann. ... > Der Hauptgrund warum C da noch so dominant ist, ist wahrscheinlich dass > Embedded Entwicklung oft von E-Technikern gemacht wird und die nur C in > Studium lernen. Im Informatik Studium hatten wir jedenfalls "Embedded > C++". Wobei man auch berücksichtigen muß, daß vieles außer den "bösen Containern" erst mit C++11 dazu kam und viele im Embeded-Bereich kommerziell genutzte Toolchains schon damit Probleme haben (laut Wikipedia-Übersichten der Compiler). Das Nichtvorhandensein der Tools ist aber kein Problem der Sprache, sondern der Hersteller, aber auch der Kunden, denn Hersteller bauen ungern, was die Kunden nicht nachfragen. OSS ist da besser dran, da reicht es, wenn die Entwickler es wollen. Ich muß aber zugeben, daß ich leicht reden hab und mir eben bei Bedarf den aktuellsten GCC selber baue und notfalls auch nichtmal GCC-offizielle Erweiterungen wie static_print auch mal selber rein-patchen kann und vor allem darf.
:
Bearbeitet durch User
Ralf schrieb: > Das war auch was, was mir durch den Kopf ging. Aber ist es nicht > umständlich, wenn die Funktionen selbst parsen müssen? Das bläht doch > immens auf, oder? Schreibe mal einen kleinen Parser, der "read 0x1234" und "write 0x1234 300" mit den Standardfunktionen versteht. Das ist mit strtok() und atoi() in wenigen Zeilen Code erledigt. Funktionsaufrufe sind billig. Jede Funktion muss selbst wissen, welche Parameter sie hat. Dann kann sie sich auch selbst darum kümmern, die rauszupopeln. In den meisten Fällen ist das nicht schwer - siehe die Beispiele von oben. In jedem Fall ist das schneller hingeschrieben und getestet als eine Parsergrammatik für yacc oder so.
Nop schrieb: > Man kann sich natürlich auch einfach einen Tokenizer schreiben (oder > strtok nehmen) und dann mit einem endlichen Automaten arbeiten. Genau das tun lex/yacc, antlr und andere Parsergeneratoren. Was die rauswerfen, ist eine Statemachine bzw. wenn man Lexer und Parser trennt, dann zwei. Der Vorteil ist hier, daß man die Grammatik in einer lesbaren Form schreiben kann. Das vereinfacht Fehlersuche und Erweiterung schon sehr. Daß man ein paar Bytes mehr Flash braucht als mit der handgeschriebenen Variante, ist mittlerweile egal. Die µC haben genug Resourcen. Und Geschwindigkeit ist bei einem Parser für eine Kommandozeile nun wirklich gar kein Thema.
Axel S. schrieb: > Der Vorteil ist hier, daß man die Grammatik in einer lesbaren Form > schreiben kann. Der zweite Vorteil ist, dass man zwar ein bisschen initialen Aufwand spendiert (sowohl bezüglich Arbeitszeit als auch memory footprint), danach aber jegliche Erweiterungen saueinfach sind, sofern man nicht gerade eine völlig verkorkste Grammatik verbockt hat. Die Codegröße steigt dann auch mit den Erweiterungen nur noch sehr langsam, da die generierten Tabellen ziemlich effizient sind. Dass den C-Code darin kein Schwein lesen kann interessiert nicht, denn man muss ja nur den Sourcecode auf lex/yacc-Ebene pflegen.
:
Bearbeitet durch Moderator
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.