command parser als library function

Begonnen von martinp876, 30 Dezember 2021, 08:56:07

Vorheriges Thema - Nächstes Thema

martinp876

Ich habe schon wieder einen Vorschlag, welcher auch schon implizit in ntf zu sehen war: ein generischer command-parser als library function.
Vorweg: Der angehängte Code ist
- funktionsfähig und getestet (bugfree? hm ) im Rahmen meiner aktuellen Spec
- beinhaltet neben get/set auch die Option attr. Attr ist komplexer und sollte getrennt betrachtet werden.
- die aktuelle Ausprägung als Modul ist nur zu studienzwecken. Sollte es wirklich die publiziert werden müsste es anders aufgehängt werden.
- ist komplett "datenfrei". Das einzige was relevant ist, ist "MCAO_cmdParser($$@)". Es wird keine initialisierung benötigt.

Die Anforderung
jedes Modul hat get/set Kommandos welche auf alles mögliche geprüft werden müssen. Weiter sind einige Module wie fhemweb auf Informationen wie "get ?" angewiesen welche performant bearbeiter werden müssem. Zusätzlich hat der User ein Recht auf eine saubere Spec der Kommandos. Der Programmierer ist gezwungen, die Syntax jedes Kommandos sauber zu definieren und der User kann diese einsehen.

Die Ziele der Implementierung
- check
  - Anzahl der Parameter minimal und maximal
  - zulässige Optionen bei "select-listen"
  - default value Festlegung  von optionalen Parametern - sichtbar für User, nutzbar für programmierer.
- high Performance bei "set/get ?"
- high performance for processing after config
- decent performance for config
- reasonable memory usage
- module and device level command definition possible
- dynamic option-list - changable on the fly w/o performance impact
- escape option for special parameter definition
- support front-end presentation (multiple, noArg,...)
- simple implementation

und wie geht das nun?
Zuerst muss der Programmierer alle seine Kommandos spezifizieren. Sollte kein wirkliches Problem für ihn sein. Ein Beispiel
mycommand = "(yes|no|maybe) (blue|red|green)+'background' [(blue|red|green|{black})]'letter color' [-myList2-] [(bold|cursiv|normal)] [(aa|bb|-myList-)] "
[]: optional param
(): select list - single value possible
()+: select list - multiple selection allowed
-xxx-: an unspecified value. Might be filled by dynamic option list.
'...': es können weitere parameter eingegeben werden. Somit wird die maximale Anzahl der parameter nicht geprüft.
"''": Kommentar, user sichtbar. Haht keine Relevanz

Im Beispiel sind 2 Parameter gefordert, maximal 6 zulässig.
Der Programmierer kann sich darauf verlassen, dass
- mindestens 3 Parameter geliefert werden: der 3. ist optional, aber ein default Wert ist definiert und wird eingesetzt
- Param 1 kann nur yes,no odermaybe sein. Alles andere wird abgewiesen
- Param 2 kann blue,red oder green sein. "blue,red" ist auch zulässig, also eine Liste. Alles andere wird abgewiesen
- param 4 ist nicht spezifiziert. Alles ist zulässig.
- Param 6 ist spannend. Hier kann aa oder bb angegeben werden - oder ein beliebiger anderer Wert.
  - allerdings kann der Programmierer einen Parameter {helper}{cmds}{dynValLst}{myList}="cc,dd,ee" setzen
    => myList wird on-the-fly ersetzt und damit ist für param6 eben nur aa,bb,cc,dd,ee zulässig.
    => sobald dynValLst gesetzt ist, ist es aktiv
Zahlen-range: [(3..112;5|off|{b})]
  Eingaben zischen 3 und 112 sind zulässig. Oder off oder "b". Da es optional ist kann es weggelassen werden. Der Programmierrer bekommt dann den Default, also "b"
  Die Schrittweite ist 5, was normiert wird. Gibt der User 6 ein wird zwischen den zulässingen Werten 3 und 8 entschieden und dahier die 8 weitergegeben.

Bei Kommandos mit einem Parameter wird der entsprechende style gesetzt (multiple,...)
Bei Kommandos ohne Parameter wird automtisch "noArg" gesetzt
Kommandos welche nicht spezifizierbar sind könnten "[-myVal-] ..." beinhalten. 0 bis 99 parameter zulässig, keine detail-info. damit kann man einzelne Kommandos faktisch aus dem parser nehmen.

Kommand-definition 2 Stufing:
Jedes Kommando kann zulässig sein für a) alle entites eines Moduls oder b) nur eine Entity.
a) wird typisch im Initialize definiert. Es steht im $modules{myModule}{helper}{cmds}{sets} oder eben get.
b) wird beim define angegeben. Es steht im $defs{myEntity}{helper}{cmds}{sets} oder eben get.

Ein re-evalutate kann notwendig sein, typisch wohl nur für devices. Wenn bspw das Device in einem anderen Mode betrieben wird könnte die Liste der Kommandos angepasst werden. Hier muss der Programmierer dann ein MCAO_cmdParser($defs{myEntity},"get",(undef,"updt")) auslösen.

DynamicOptionLists benötigen kein re-eval. Diese können auf Device oder Modul Ebene definiert werden.

Um die Funktion zu nutzen muss der Programmierer folgenden Code einbauen (sinngemäß für get ebenso)
sub <moduleName>_Set($@) {
  my ($hash, @a) = @_;

  my  ($chk,@defaults) = MCAO_cmdParser($hash,"sets",@a);
  return undef if ($chk && $chk eq "done");
  return $chk  if ($chk);
  push @a,@defaults;


Eine Überlegung wert?
Bei meinen privaten Modulen im Einsatz.

justme1968

#1
das verarbeiten solcher parameter listen zu standardisieren finde ich gut. neben set, get und attr betrifft das im übrigen auch noch define.

ich finde dafür vor allem benannte parameter wichtig da sie positions until abhängig, leicht zu erweitern und 'selbstdokumentierend' (soll heißen: lesbar) sind. außerdem ermöglichen sie fhem weite standard parameter die durch das framework ausgewertet werden (z.b. attribute direkt im define mit zugeben). wenn ich deinen vorschlag richtig verstanden habe sind zumindest aktuell keine benannten parameter vorgesehen.

ich hatte vor einer weile dafür als einen ersten schritt parseParams vorgeschlagen das inzwischen auch global eingebaut ist und von einigen modulen verwendet wird. parseParams legt aktuell folgendes fest:
- prinzipielle syntax für die kommandozeile inklusive benannter parameter, werten mit leerzeichen
  oder anführungszeichen, ...
- schnittstelle/format der rückgabe für den entwickler (hash für benannte parameter, array für die
  restlichen positionalen parameter).
- mögliche prüfung auf perl syntax in parametern

eine formale notation für die möglichen argumente (mit parseParams selber auswertbar) und die erzeugung von hilfe, fehlermeldungen und '?' ausgabe d.h. ein vor- und nachverarbeitungsschritt für parseParams wäre ein möglicher nächster schritt.

die möglichkeit die attrList in dieser neuen notation anzugeben und einbauen von etwas wie eine setList und getList (und ...Changed routinen) um den vorverarbeitungsschritt global zu verankern ein weiterer.

ich fände es gut wenn die (wenn auch rudimentär) bereits vorhanden möglichkeiten schrittweise erweitert werden statt alles komplett über den haufen zu werfen und bei null anzufangen.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Beta-User

Hi Martin,

grundsätzlich finde ich diesen Vorschlag auch sehr interessant, und es gab afaik auch diverse Anfragen in der Vergangenheit von einzelnen Maintainern, wie man sinnvolle Einschränkungen bei den Settern "weiter hinten" vercoded.

Die parseParams-Frage hatte ich mir auch gleich gestellt, wobei
Zitat von: justme1968 am 30 Dezember 2021, 10:41:14
und von einigen modulen verwendet wird.
m.E. übertrieben ist. Bei der Überarbeitung von RHASSPY habe ich das auch nach "globaler" parse-Params-Vorverarbeitung umgestellt, allerdings kein Beispiel unter den existierenden Modulen gefunden, bei denen das auch der Fall wäre (nicht mal HUE.*)

Zitat
ich fände es gut wenn die (wenn auch rudimentär) bereits vorhanden möglichkeiten schrittweise erweitert werden statt alles komplett über den haufen zu werfen und bei null anzufangen.
+1

Im Moment kann ich aber nicht erkennen, dass es sich grundsätzlich in die Quere kommen müßte, "spannend" finde ich es bei "Multi-Arrays" wie sie z.B. für "speak"-Befehle oder msg-Kommandos typisch sind.

Fände es gut, wenn das Eingang in die fhem/lib-Funktionen fände (es braucht ja keine direkte Unterstützung durch fhem.pl, oder?) und man "sowas" einfach per "use" einbinden könnte.

Grüße, Beta-User
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

martinp876

Define habe nich noch nicht im Angebot - ist aber grundsätzlich kein Problem. Deutlich einfacher als attr. Zu Attr werde ich noch einiges sagen wenn die anderen klar sind.

Zitatleicht zu erweitern und 'selbstdokumentierend' (soll heißen: lesbar) sind.
was ich nicht erwähnte ist, das 2 get kommandos gleich komplett abgewickelt werden: "get list (default|hidden|module)" und "get cmd". Damit will ich anregen, dass gewisse Kommandos generisch, also in der Library verarbeitet werden können. List gehört dazu und auch das Auflisten der Kommandos. Vielleicht findet sich mehr.

Zitateiner weile dafür als einen ersten schritt parseParams vorgeschlagen
cool. Wo finde ich die Dokumentation hierzu?
auf den ersten Blick scheint es viele Optionen für die Syntax zu geben - ist das so? Ist das notwendig?

Zitatvorhanden möglichkeiten schrittweise erweitert werden statt alles komplett über den haufen zu werfen und bei null anzufangen
wenn ich die Dokumentation von parseParams habe kann ich prüfen, wie kompatibel das ist.
Bedenke, dass mein Vorschlag quasi 1:1 in jedes vorhandene set/get eingebaut werden kann (die Kommandos müssen definiert werden, klar). Einige Prüfungen wären dopplet, kann man be gelegenheit entfernen.

Bei Attr ist einiges mehr zu tun. Der Anspruch ist, nicht definierte Attribute abzuweisen. Bspw muss erst ein userAttr definiert werden bevor dies genutzt wird. Wenn die Definition im userAttr entzogen wird ist dies auch zu löschen!
Attribute werden beim Booten in wilder Reihenfolge angegeben. Eine Korrelation zur Reihenfolge der Defines ist schon gar nicht zu sehen. Hat man attribute welche abhängigkeiten haben (einfaches Beispiel ist das IOdevice welches auch exitieren sollte) kann man erst prüfen, wenn alles gelesen ist. Meine Module lassen demnach pre-init_done alle attribute zu. post-init muss alles geprüft werden. Hierzu habe ich einen trigger vorgesehen. We mag nicht alle Module betreffen, ich habe typisch das Problem.
Ich lege sehr viel wert auf die Dynamical-Options in den parametern - und den schönen Select-Listen im Frontend. Bei set/get geht das on-the-fly - fhemweb fragt bei jeder Nutzung nach und die Daten können angepasst werden. Bei Attr ist es ein Problem, da die Listen interlegt werden. D.h. die Listen müssen erstellt werden, wenn sich die Dynalical-Optionlist ändert. Das ist mehr Aufwand (der sich m.E. lohnt).
Es ist nicht wirklich komplex, da der Modulschreiben seine Daten kennt und bei Bedarf einfach den Trigger auslösen muss (Funktionsaufruf "update")

ZitatEingang in die fhem/lib-Funktionen fände (es braucht ja keine direkte Unterstützung durch fhem.pl, oder?) und man "sowas" einfach per "use" einbinden könnte
hm. 1) die Funktion ist komplett autark. Das aufrufende Modul wird mit parametern beglückt - zur schnelleren Verarbeitung. Wer es nicht nutze wird nicht behelligt. Keine Abhängigkeiten.


p.s. habe gerade einmal parsParams angesehen. Ist schon etwas anderes, eine andere Idee.
Warum machst du statt
my $count = 0;
      for my $i (0..length($value)-1) {
        my $c = substr($value, $i, 1);
        ++$count if( $c eq '{' );
        --$count if( $c eq '}' );
      }

nicht

my $valTmp = $value;
my $count = ($valTmp =~s/{//g) - ($valTmp =~s/}//g);

Sollte performanter sein, als durch schleifen zu tickern substr aufzurufen.
Das kommt 2-mal vor

justme1968

Zitatm.E. übertrieben
übertrieben ist relativ. ein einfaches grep gibt etwas über 100 treffer plus verwendung in fhem plus die verwendung in den modulen die es global für get, set und attr verwenden.

ist also garnicht so schlecht :)

ZitatWo finde ich die Dokumentation hierzu?
im wiki

Zitatauf den ersten Blick scheint es viele Optionen für die Syntax zu geben
was meinst du damit?
es gibt benannte paramter der form <name>=<wert> und unbenannte paramter der form <wertN>. beides jeweils mit der möglichkeit leerzeichen im wert zu erlauben. das ist manchmal nötig. da es auch " oder ' im wert geben kann braucht man auch hier beides um das jeweils andere in kombination mit leerzeichen zu erlauben. zusätzlich ist auch perl coder für benannte und unbenannte paramter erlaubt. das war alles.

die möglichkeit den seperator explizit anzugeben gibt es vor allem weil parseParams auch intern z.b. für die plots verwendet wird. das war eher ein praktischer nebeneffekt mit geringem aufwand.


das mit den attributen ist glaube ich ganz unabhängig vom parsen ein mehr oder weniger großes problem. auch das attribute im define noch nicht gesetzt sind es aber eventuell hier schon abhängigkeiten gibt. auch die abhänigkeit von device und io in der reihenfolge ist nicht schön. für hue werte ich dafür im define ein IODev=<name> aus (da ich hier schon wissen muss zu welcher bridge ein device gehört da die laufenden device nummern im aktuellen api leider nicht bridge übergreifend eindeutig sind) und verwende erst danach die 'normalen' fhem wege. aber das ist glaube ich unabhängig vom parsen um das es hier geht eine ganz andere baustelle.

klar kann man immer optimieren. manchmal ist es aber besser der code bleibt einfacher verständlich. im konkreten fall ist die optimierte version etwas über zwei mal so schnell. aber wir kommen (je nach hardware) erst bei einer million aufrufe auf eine gesamtzeit im bereich von 15 bzw. 6 sekunden. da die meisten aufrufe von parseParams beim start oder benutzer interaktivem code und nicht in einer cpu-bound umgebung erfolgen halte ich das noch für vernachlässigbar. ich gebe aber zu das eine fritz!box für mich nicht mehr zur für fhem relevanten hardware zählt. da mag das anders aussehen.


vor allem kommt es mir darauf an die syntax mit benannten und unbenannten parameter und der art und weise wie leerzeichen, anführungszeichen und perl code als wert möglich ist auch bei weiteren routinen zum parsen und solchen dingen wie automatische usage oder hilfe meldungen möglich und kompatibel bleiben. dann haben wir alle etwas davon.

mir fällt gerade noch etwas ein was zwar nur bedingt hier her gehört, aber durchaus berührungspunkte hat: eine möglichkeit die argument liste für parameter zwei- oder mehrstufig zu machen. aktuell ist ja für jedes kommando nur eine einzige paramter liste möglich. es ist aber an ein paar stellen schon die frage und das bedürfnis nach einer zweistufigen auswahl (d.h. z.b. ein drop down dessen auswahl die möglichen werte in einem zweiten drop down bestimmt) aufgekommen. bisher ist das vor allem an einer fehlenden idee wie man das ganze für set/get/attr ? aufbereitet und anzeigt ohne das es zu konflikten mit der existierenden syntax kommt. vielleicht hast du hier ja eine idee.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Beta-User

Zitat von: justme1968 am 30 Dezember 2021, 21:22:15
übertrieben ist relativ. ein einfaches grep gibt etwas über 100 treffer plus verwendung in fhem plus die verwendung in den modulen die es global für get, set und attr verwenden.
Na ja, mir ging es hauptsächlich um den letzten Punkt, und da hatte ich Mühe, ein Modul zu finden, in dem das so ist. Kann schon sein, dass es eines gibt, ich habe die Suche dann aber irgendwann abgebrochen.
Ändert aber nichts daran, dass es eine coole Sache ist und ich  es in RHASSPY sowohl global implementiert wie auch an weiteren (9, wenn ich richtig gezählt habe) Stellen "solo"! (Bin grade am Überlegen, ob WeekdayTimer nicht in diese Richtung auch umgestellt werden kann/soll...).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

martinp876

Korrekt - es gibt eine erhebliche Anzahl User von parseParams
Ich habe einmal ein Review gemacht
Zitatklar kann man immer optimieren. manchmal ist es aber besser der code bleibt einfacher verständlich.
keine Zustimmung. Ich sehe nicht, dass das verständlicher ist. Diese Funktion wird, wenn sich einschlägt, etliche Male aufgerufen. Performane erwarte ich von einer Library-funktion. Verständlich muss die Beschreibung sein!
ParseParams könnte bei jedem Kommandoaufruf angewendet werden.

1) separator split only if $cmd not a pointer to an array
2) the list of params will ignore "". I.e. the given list of entries may change. Keys will be removed anyhow from the
3) in case of key with empty value the keyvalueseparator will be removed eventhough it is not threated as key
   "myKey=myValue" will be presented as myKey => myValue
   "myKey=" will be presented as "myKey". "=" is removed, no hash
4) what if "=" is given - key will be undef
5) what if there is a '^"' but no '"$'? Join will happen - but not the remove or "'
6) will check for curled brackets over params. "}}} {{{" will pass
7) assume separator " " and joiner "_". Result for
   "{fhem 'set melight on'}"
   {fhem_'set_mylight_on'}

Wäre folgender Code nicht performanter?
   if($value =~ m/^(["'])/){
     my $sep = $1;
     while( $value !~ m/$sep$/ ) {
       my $next = shift(@params) // last;
       $value .= $joiner . $next;
     }
     if($value =~ m/$sep$/ ) {
       $value =~ s/^.(.*).$/$1/;
     }     
   }

Sorry, wie gesagt, bei operational performance - insbesondere bei Library-funktionen kenne ich keine Gnade.
In deinem Code wird ein langer String "{hallo ich bin ein langer String und will zerlegt werden}" tatsächlich zeichen für Zeichen zerlegt. Es gibt hier viele Möglichkeinten, das besser zu machen. Native Kommandos einer Programmiersprache sind typisch um Faktoren schneller - und schlagen ein substr locker. Da muss ich noch nicht einmal nachmessen.
=> ich würde ein pimpen vorschlagen - kostet fast nichts

Aber das war ein anderes Thema. Hier geht es um "cmdParser". Will man parseParams integrieren/addieren oder sonst etwas muss ich einmal nachdenken.
Das Ziel von cmdParser ist, in der üblichen Infrastruktur eine
- einheitliche
- non-duplicate
- easy to use
- seamless
- performance-optimized
Funktion zu Verfügung zu stellen.
Aktuell werden kommando-parameter als array zu Verfügung gestellt. In Kommandos sind typisch keine Kommentare enthalten, keine Keys,...
==> aktuelle Spec für cmds ist (korrigiere mich)
$input ="set myEntity myCmd param1 param2 'param3 param4'"
ein @array = split("\s",$input)
#> $a[5] = "'param3"
Sollte die Spec geändert werden und ' oder " oder { betrachte werden kann man
#> $a[5] = "'param3 param4'"
auch auswerten. Mache ich gerne - aber die basis-spec muss erst einmal her (liegt die auch schon irgendwo?)

Aber: cooles Wiki - ich muss mehr suchen :)


martinp876

so - wie immer bin ich etwas langsam - die Ideen reifen gemächlich.
cmdParser prüft auf Anzahl Parameter und Wert (abstrakt). Nicht beinhaltet ist eine Prüfung auf "Typ". Hierzu wäre ein Parameter als Typ zu kennzeichnen, bspw "perl-code".
Aber von vorne:
cmdParser prüft ein Array von Parametern - das Zerlegen des Input-Strings macht der Kernel (bislang). Das würde ich erst einmal grundsätzlich so beibehalten - zu mindest als default.
=> neue Anforderung: Parameter-clustering
Ausgehend vom default Zerlegen nach [ \t]  können parameter wieder zusammengefasst werden. Der Kernal wird das nicht mehr unterstützen, da zu viele sich darauf verlassen. Allergings könnte man es optional anbieten: "joinGroups". Entsprechend parsParams wäre die Regel: ["'{] werden - in dieser Reihenfolge - zusammengefasst. Die 3 Zeichen sind jeweils als Anfang/Ende eines Parameters zu sehen.
Ich habe einmal gespielt. Aus dem typischen @a kann man @rt machen. Sollte zügig gehen ablaufen.


sub params2combinedparams(@){
my $b = " ".join(" ",@_)." ";
my @rt;
while($b){
    if($b =~ s/(.*?) (\".*?\"|'.*?'|\{.*?\}) / /){
        my ($pre,$s)=($1,$2);
        push@rt,$_ foreach(split" ",$pre);
        push@rt,$s;
    }
    else{
        push@rt,$_ foreach(split" ",$b);
        last;
    }
}
return @rt;

Das beinhaltet das zusammenfassen aus parseParams - allerdings ohne "keys" handling.
Keys handling ist ... für "kenner". Keys werden - egal wo sie stehen, aus dem Array entfernt. da muss man vorsichtig sein.

Zurück zu cmdParser:
man kann also @a = params2combinedparams(@a) vor dem parser aufrufen... oder es als option in den cmdParser aufnehmen.

martinp876

#8
und wenn man es fertig denkt ist hier der code zu parseParams - allerdings ohne eigene separatoren o.ä.

sub parseParams(@){
my $b = " ".join(" ",@_)." ";
my @rt;
while($b){
    if($b =~ s/(.*?) ([^\s]*=?\".*?\"|[^\s]*=?'.*?'|[^\s]*=?\{.*?\}) / /){
        my ($pre,$s)=($1,$2);
        push@rt,$_ foreach(split" ",$pre);
        push@rt,$s;
    }
    else{
        push@rt,$_ foreach(split" ",$b);
        last;
    }
}
my %h= map{split("=",$_,2)}grep/=/,@rt;
return (grep!/=/@rt,%h);
}

Will man anstelle eines "@cmd" ein $cmd eingeben kann der User s selbst spalten
my (@a,%h) = parseParams(split(" ",$cmd));
So viel würde ich programmierern nun schon zutrauen.
Ich halte es für übersichtlicher als deine Variante (Ansichtssache), schneller sowieso.

Zum Quick-test einfach einmal als einzeiler in die Kommandozeile pasten

alles als Array:
{my $b = " ".join(" ",split("[ \t]","pre pre myKey={hihi haha} 'hallo hier1' und \"hhhh sdj asldkhf \" 'ich a' {hallo hier2} xx yy zz"))." ";;my @rt;;while($b){if($b =~ s/(.*?) ([^\s]*=?\".*?\"|[^\s]*=?'.*?'|[^\s]*=?\{.*?\}) / /){my ($pre,$s)=($1,$2);;push@rt,$_ foreach(split" ",$pre);;push@rt,$s;;}else{push@rt,$_ foreach(split" ",$b);;last}};;join("\n",@rt)}


denhash
{my $b = " ".join(" ",split("[ \t]","pre pre myKey={hihi haha} 'hallo hier1' und \"hhhh sdj asldkhf \" 'ich a' {hallo hier2} xx yy zz"))." ";;my @rt;;while($b){if($b =~ s/(.*?) ([^\s]*=?\".*?\"|[^\s]*=?'.*?'|[^\s]*=?\{.*?\}) / /){my ($pre,$s)=($1,$2);;push@rt,$_ foreach(split" ",$pre);;push@rt,$s;;}else{push@rt,$_ foreach(split" ",$b);;last}};;my %h= map{split("=",$_,2)}grep/=/,@rt;;join("\n",map{"$_ = $h{$_}" }keys %h)}

das Array bereinigt um Keys
{my $b = " ".join(" ",split("[ \t]","pre pre myKey={hihi haha} 'hallo hier1' und \"hhhh sdj asldkhf \" 'ich a' {hallo hier2} xx yy zz"))." ";;my @rt;;while($b){if($b =~ s/(.*?) ([^\s]*=?\".*?\"|[^\s]*=?'.*?'|[^\s]*=?\{.*?\}) / /){my ($pre,$s)=($1,$2);;push@rt,$_ foreach(split" ",$pre);;push@rt,$s;;}else{push@rt,$_ foreach(split" ",$b);;last}};;my %h= map{split("=",$_,2)}grep/=/,@rt;;join("\n",grep!/=/,@rt)}



justme1968

ZitatIch halte es für übersichtlicher als deine Variante (Ansichtssache)
ist in der tat ansichtsache :). ich halte es nicht für übersichtlicher, für einen neueinsteiger oder wenn man sich den code eine weile nicht angeschaut hat erst recht nicht.

auf die schnelle fällt mir auf:
- mein perl mag die version nicht: Array found where operator expected at x.pl line 24, near "/=/@rt"
(Missing operator before @rt?)
syntax error at x.pl line 24, near "/=/@rt"
Execution of x.pl aborted due to compilation errors.
das ist das return am ende.
- die version ist nicht vollständig kompatibel, die rückgabe werte ist keine ref
- das ergebnis ist ebenfalls ein anderes als bei der aktuellen version für
  den unittest aus fhem/t/FHEM/fhem.pl/00_parseParams.t
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

martinp876

#10
In Sachen Übersichtlichkeit sind wir klar nicht auf einer Linie ;)

Nun, ich habe es auf die Schnelle codiert. Alles in der Commandline. Wenn der Returnwert angepasst werden muss, ok - sollte machbar sein.
return (grep!/=/@rt,%h); # ein Komma vergessen
==>
return (grep!/=/,@rt,%h);
###> Bevor ich die Funktion abgebe werde ich es noch einmal testen. Allerdings ist es nicht meine Funktion , also werde ich nichts abgeben.

In meiner Version sind die Klammern auf korrekte Anordnung geprüft... allerdings habe ich noch nicht auf mehrfache Klammern geprüft... ok, eine Lücke.
Zitatfür einen neueinsteiger oder wenn man sich den code eine weile nicht angeschaut hat erst recht nicht
unklar, was du mir sagen willst.
a) Das ist (wäre) eine Library function, kein Tutorial. Ist mir also egal, ob Einsteiger den Code verstehen.  Prüfst du bei Compilern die Sources?
b) der Ablauf ist m.E. leicht zu durchsteigen
   - array wird zum String zusammengehängt
   - String wird nach "...", {...} oder '...' durchsucht.
      wenn gefunden wird alles, was davor steht wieder zerlegt und in ein Array gepuscht, der Findling danach ebenfalls
      Das gefundene wird entfernt (in einem Zug)
   - suche wird wiederholt
   - der Rest wird ebenfalls zerlegt und ins array geschaufelt

Das regex liest sich nicht wie ein Bilderbuch - muss es das?

Was ist dein primäres Ziel (deine Antwort verwundert mich erheblich):
  A) effizienter code, schell, sparsam,...
  B) einfach zu lesender code, damit möglichst Einsteiger ihn lesen können

Mit B) kannst du bei mir nicht punkten - schon garnicht im Kernel / Library Bereich

So, die Verbesserung:
Was jetzt nicht passt ist die mehrfach Klammerung, also "{adf {asdf asdf } asdf}" => das geht schief...
Bei " ist dies kein Problem, da hier nicht zwischen Anfang ' "' und Ende '" ' unterschieden und gezählt wird. Habe ich bei dir auch nicht gesehen.


sub parseParams(@){
my $b = " ".join(" ",@_)." ";
my @rt;
while($b){
    if($b =~ s/(.*?) (([^\s]*=+)\".*?\"|([^\s]*=+)'.*?'|([^\s]*=+)\{.*?\}) / /){
        my ($pre,$s)=($1,$2);
        push@rt,$_ foreach(split" ",$pre);
        push@rt,$s;
    }
    else{
        push@rt,$_ foreach(split" ",$b);
        last;
    }
}
my %h= map{split("=",$_,2)}grep/=/,@rt;
return (grep(!/=/,@rt),%h);
}

martinp876

So, jetzt sieht es schon komplizierer aus... die Klammern werden gezählt.
Wie auch bei dir ist das ganze nicht so einfach.
Es ist vorschrift dass ein space oder ein Key mit "=" vor den Beginn-zeichen steht. Dem Ende-Zeichen folgt immer ein space
also
" {xxx{yyy} } "
macht Schwierigkeiten. Die 2. Klammer auf hat kein pre-space
In deinem Beispiel auf der Webseite fehlt
test11={ { my $abc ="xyz" }}
test12={{ my $abc ="xyz" } }
test13={ { my $abc ="xyz" }
test6a='test6=test6
test14="hallo"hali
test15 ={ { my $abc ="xyz" }}
test16={ }} my {$abc ="xyz" } }

=> all the rainy das scenarios
Ich denke bei 16 verzählt du dich.
...
Ich gehe davon aus, dass es hier um das Parsen von Endanwender-Daten geht. Man muss also mit JEDER! Eselei rechnen.
Auch mein code bietet noch kein Error-handling. Das brauche ich aber nur, wenn $i kleiner null wird, da habe ich es einfacher als du, da ich das Ende schon kenne, wenn ich Anfange. Du hoffst, dass es ein Ende gibt, wenn ein Anfang zu sehen ist.


sub parseParams(@){
my $b = " ".join(" ",@a)." ";
my$i=0;
while($b=~m/([ =]{|} )/){
    if($1 eq"} "){
        $i--;
        $b =~s/(})( )/$1$i$2/;
    }
    else{
        $b =~s/([ =])({)/$1$i$2/;
        $i++
    }
}
$b=~ s/[1-9]{/{/;
$b=~ s/}[1-9]/}/;
my @rt;
while($b){
    if($b =~ s/(.*?) (\".*?\"|'.*?'|([^\s]*=+)0\{.*?\}0) / /){
        my ($pre,$s)=($1,$2);
        $s=~s/0{/{/;;$s=~s/}0/}/;
        push@rt,$_ foreach(split" ",$pre);
        push@rt,$s;
    }
    else{
        push@rt,$_ foreach(split" ",$b);
        last
    }
}
return (grep(!/=/,@rt),%h);
}

zap

Man könnte auch Standard-Mechanismen wie LEX(X) benutzen (z.B. Perl::Lex), um einen Syntax-Checker zu bauen. Das spart möglicherweise etwas Arbeit.
2xCCU3, Fenster, Rollläden, Themostate, Stromzähler, Steckdosen ...)
Entwicklung: FHEM auf AMD NUC (Ubuntu)
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: FULLY, Meteohub, HMCCU, AndroidDB

martinp876

LEX kenne ich nicht (wie so vieles) - hört sich gut an.
Allerdings ist die anfängliche Idee eines cmdParsers hiervon nicht beeinflusst.
Der Ablauf abstrakt:
1) Das Kommando ist definiert ( muss ja wohl)
2) Das Kommando ist syntaktisch beschrieben. (Dokumentation ist notwendig aber hier kein Thema)
3) der User ruft ein Kommando mit parametern auf
4) parseParams o.ä. könnte das zerlegen des Strings in Parameter abweichend von "split("[ \t],$cmdString)" gestalten. bspw "xxx yyy" zu einem param zusammenfassen
5) ein Array von Parametern ist so oder so erstellt und muss verarbeitet werden.
6) cmdParser prüft die Parameter "inhaltlich", also nach schlüsselwörtern, Anzahl,....
7) cmdParser erzeugt responses im Fall von mismatch zur Spec - und versorgt fhemWeb mit option-list wenn erforderlich.

=> in den meisten Fällen ist m.E. ein paramParser nicht notwendig. Es ist aber kein Problem, bspw ".*" zusammenzufassen. 

Beta-User

Zitat von: zap am 01 Januar 2022, 12:26:00
Man könnte auch Standard-Mechanismen wie LEX(X) benutzen (z.B. Perl::Lex), um einen Syntax-Checker zu bauen. Das spart möglicherweise etwas Arbeit.
An sich eine gute Idee, aber bezogen auf den konkreten Vorschlag:

corelist -d Perl::Lex

Data for 2020-02-20
Perl::Lex was not in CORE (or so I think)

Wenn man (unbemerkt/unbeabsichtigt) solche Abhängigkeiten in viele Module einbaut, muss auch das Umfeld dazu passen, also z.B. indem das zugehörige Debian-Paket (ich habe auf die Schnelle keines gefunden) mit in die Installationsroutine eingebaut wird.

(Verwirrung bei den Usern durch die Verwendung von cpan bzw. das Nebeneinander von cpan und Distri-Packages sind Legion)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files