Ausbalanzierte Stringsequenzen

  • Ich möchte mal etwas Brainstorming mit euch betreiben.


    Ziel: Ausbalanzierte Stringsequenzen in (auch geschachtelten) Strings erkennen.

    Mir geht es darum, in Programmzeilen, die aus der au3 Datei gelesen werden, die Stringbereiche eindeutig zuordnen zu können.

    Im Editor selbst ist die Zuordnung recht unproblematisch, da dort jeder Position auch der Style zugeordnet ist (editor.StyleAt[pos]).

    Wenn ich z.B. folgende fiktive Programmzeile habe:

    _SomeFunc($param1, '"', ';', "'", $param5, "text, text, text", '"') ; Kommentar, hat auch "string"


    Hier zeigen sich auch schon Probleme:

    - kann nicht als Parametertrenner gelesen werden, da evtl. auch Bestandteil von Parametern oder Kommentar

    - " kann selbst ein Parameter sein, aber auch Stringbegrenzer (oder auch zum Maskieren von sich selbst im String)

    - Stringbegrenzer als oder erleichtern es auch nicht unbedingt und das Bsp. kann man natürlich noch unberechenbarer gestalten.


    Wahrscheinlich bleibt hier nur die Lösung, wie sie vermutlich auch vom Interpreter durchgeführt wird: Regex mit Subpattern-Definitionen, sieht z.B. so aus:

    Wobei ich (obwohl ich Regex mag) hier mit der PCRE-Syntax etwas fremdele. Ich habe vor längerer Zeit mal mit Oniguruma gearbeitet, ist zwar ähnlich (alle Regex Dialekte ähneln sich irgendwo) - aber lag mir mehr. Naja, das ist aber nur Gewohnheitssache.


    Im obigen Bsp. habe ich einen Funktionsaufruf, dadurch lässt sich der Kommentar sauber abtrennen. Habe ich aber Variablenzuweisungen mit Text (der enthält) wird es schon wieder unangenehmer und die ausbalanzierte Erkennung des Strings ist zwingend notwendig.


    Also Lösungsvariante #1 ist, dass ich mir das Eierlegende-Wollmilchsau-Regex-Pattern bastele. :rofl:

    Wenn ihr ein paar Gedanken beibringen möchtet, bin ich sicher nicht böse. :whistling:

  • Ich würde die Zeile einfach von links nach rechts durchgehen. Es gibt ja "nur" zwei Zeichen (chr(34) und chr(39)), die einen String kennzeichnen.

    Wenn eines der beiden Zeichen auftritt, einen Startpunkt setzen und darauf warten, dass das Zeichen ein zweites Mal auftaucht. Bis dahin ist es dann ein String.

    Hier mal als AutoIt-Code:

    Die "test.txt"-Datei enthält Deine obige Vorgabe ("fiktive Programmzeile").

  • - " kann selbst ein Parameter sein, aber auch Stringbegrenzer (oder auch zum Maskieren von sich selbst im String)

    Suchst du sowas?:

    https://regex101.com/r/6D8QX1/2


    - , kann nicht als Parametertrenner gelesen werden, da evtl. auch Bestandteil von Parametern oder Kommentar

    Wäre möglich aber ekelhaft umständlich, dass mit der obrigen Stringdefinition so zu verheiraten, dass Kommas innerhalb von Kommentaren ignoriert werden.
    Vielleicht wäre es für dich eine Option die Kommentare einfach vorher zu killen.

    Z. B. so:

    AutoIt
    StringRegExpReplace($s_Code, '((?m)^\h*\#(?>cs|comments-start)\b(?:(?sU)(?:(?R)|.)*)^\h*\#(?>ce|comments-end)\b.+\R|(?m)^(?>(?<!")"(?>"")*.*?(?<!")"(?>"")*(?!")|(?<!'')''(?>'''')*.*?(?<!'')''(?>'''')*(?!'')|[^\;\r\n])*?\K\h*\;.*$)', '')
  • Oscar

    Nach dieser Methode habe ich auch meine "Balanced Pairs" erstellt. Da ich damit jedoch den String rekursiv durchforstet habe, führte das mit Stringbegrenzern nicht zu sinnvollen Ergebnissen.


    AspirinJunkie

    Die Subpattern Definition sieht schon mal super aus. :thumbup:

    Das Pattern zum Kommentare killen muss ich mal auf meine Skripte loslassen. Meines war etwa halb so lang und hatte ein paar mögliche Fehlerkennungen. :whistling:

    Kommentare muss ich eh entfernen. Im zweiten Schritt werden alle Zeilenfortschreibungen aufgehoben und dann kann ich mit meinem eigentlichen Parsen beginnen.


    Danke erst mal für eure Ideen.

    Wenn ich es mit meinem Projekt verheiratet habe, gibt es das natürlich hier im Forum.

  • Bis vorhin war ich noch dabei, den OranizeIncludes ein wenig umzustricken, wobei mir dann die Idee kam, mal in den IncludesHelper zuschauen, wie er das da so macht. Da habe ich dann gesehen, dass er da eine "DelComment.dll" als Binary drin hat, die er mit:

    $var = MemoryDllCall($DllHandle, 'str', 'DelComment', 'str', $sFile, 'int', $onlyINCL)

    aufruft... ich habe das Teil dann mal als Dll gespeichert und die Dll mit deiner fiktiven Programmzeile konfrontiert. :D


    So sieht das Ergebnis aus:

    ;~ _SomeFunc($param1, '"', ';', "'", $param5, "text, text, text", '"') ; Kommentar, hat auch "string"

    ;~ _SomeFunc($param1, '', '', "", $param5, "", '') ; <<<===


    Ok, ist also nicht zu gebrauchen und den IncludesHelper werde ich dann wohl besser nicht mehr benutzen. Es ist aber sicher keine schlechte Idee, diese Dinge rund um Includes mit einer Dll (Nim-Dll?) zu erledigen.


    Wollte es euch aber nicht vorenthalten... wenn ich die Dll hier uppen soll, gebt Bescheid.


    OranizeIncludes... die Frage zu rekursivem Suchen nach Includes in Includes ist hiermit ja wohl beantwortet:

    AutoIt
    Func _getIncludesOfIncludes()
    ; #### TO DO
    EndFunc ;==>_getIncludesOfIncludes

    Hm, recursiv... ist das denn nicht eher ein iteratives Durchsuchen?


    So, jetzt noch schnell ne Mütze voll Schlaf nehmen...


    Bye ;-)

  • Ich bin mir nicht ganz sicher was genau du versuchst, wollte meinen Senf aber trotzdem abgeben, weil bei RegEx und Interpreter die Alarmglocken bei mir schrillen.


    Ganz dunkel klingelte bei mir noch was aus der theoretischen Informatik das mir sagt "Nein, das geht so nicht". Reguläre Ausdrücke sind für sowas nicht "mächtig" genug um alles abzudecken, und auch in der Praxis wird ein Compiler keinen RegEx nutzen und bei AutoIt würde es mich auch wundern, wenn es so umgesetzt wurde (falls überhaupt möglich, wozu ich jetzt komme). Es gibt da die Chomsky Hierarchie, mit der man beschreibt in welcher Kategorie irgendeine beliebige Sprache ist, Deutsch, C++, AutoIt, Babysprache, ... Dabei sind reguläre Ausdrücke (im Sinne der TheoInf) das "schlechteste", im Sinne von sie können am wenigsten beschreiben (und sind in der Hierarchie ganz unten). Durch das "zurückgehen" in Computer RegEx sind sie nicht mehr in der untersten Stufe, aber ich vermute, dass sie damit auch noch nicht in der höchsten sind. Anscheinend sind syntaktisch korrekte Programme in den meisten Fällen kontextfrei (die Stufe nach Regulären Ausdrucken), aber kompilierbare nicht, wie hier angegeben ein Beispiel. Bei Interesse findet sich hierauch noch ein Beispiel, wo der Compiler zum erfolgreichen Kompilieren eine Primzahlberechnung durchführen muss, was ebenfalls nicht kontextfrei ist.


    Long story short: Einen eierlegenden RegEx zu finden halte ich für eine sehr schlechte Idee, das Ding zu parsen für sinnvoller.


    Das mal meine Gedanken und ein Exkurs in die TheoInf.

    Es gibt sehr viele Leute, die glauben. Aber aus Aberglauben.
    - Blaise Pascal

  • Das Pattern zum Kommentare killen muss ich mal auf meine Skripte loslassen. Meines war etwa halb so lang und hatte ein paar mögliche Fehlerkennungen. :whistling:

    Wenn du es als Subpattern schreibst wird es auch deutlich übersichtlicher:

    Andersherum kannst du natürlich auch die Stringdefinition als ganz normales Pattern umschreiben:

    Code
    "(?>[^"]+|"")*"|'(?>[^']+|'')*'


    Xorianator

    Kommt darauf an was hier das Ziel ist.

    Einen Gesamtparser nur in Regex - ne das ist wirklich bullshit.

    Aber einzelne Elemente in RegEx definieren um diese für einen Parser mitzuverwenden - das kann durchaus Sinn machen.

    So habe ich z.B. in meinem JSON-Parser gemacht.

    Dieser parst sequenziell mit AutoIt aber nach jedem Element wird per Regex geschaut mit was es weitergeht.

  • Hm, recursiv... ist das denn nicht eher ein iteratives Durchsuchen?

    - Öffne Datei-1

    - Finde Include1 - Öffne dieses (Sub)Include und finde dort ein weiteres Include usw. usf.

    - Finde Include2- Öffne dieses (Sub)Include ......


    Das ist m.M. ein rekursives Vorgehen (auf das, was ich gefunden habe, erneut mit demselben Verfahren zugreifen)

    In dem Bsp. im obigen Post habe ich die Funktion BalancedPairs, die ich schon mal als Einzelfunktion erstellt hatte, aufgesplittet in Einzelfunktionen. Die Einzelfunktion hatte sich nach jedem Fund selbst aufgerufen um weiterzusuchen (eindeutig rekursiv). In der neuen Version ist das Vorgehen aufgesplittet. Somit eher keine Rekursion.


    Ich bin mir nicht ganz sicher was genau du versuchst

    Im Moment sammele ich erstmal Lösungsansätze. Ich habe zwei Baustellen (GetUnsusedVars, ManageIncludes), die beide auf derselben Basis arbeiten (sollen) --> On-The-Fly müssen Dateien und Includes (und Includes in Includes..) durchsucht werden.

    Ich werde sehr wahrscheinlich den zeitintensiven Part auslagern (Nim). Aber das steckt alles noch am Anfang, wobei das Vorgehen selbst von der Sprache unabhängig ist.


    Einen eierlegenden RegEx zu finden halte ich für eine sehr schlechte Idee, das Ding zu parsen für sinnvoller.

    Die eierlegende Wollmilchsau zu finden war auch eher scherzhaft gemeint. Aber RegEx wird für einzelne Prüfungen/Selektionen sicher dabei sein.


    Wenn du es als Subpattern schreibst wird es auch deutlich übersichtlicher:

    Ich habe jetzt mal einige Pattern aus meiner Patternsammlung umgeschrieben in Subpattern-Definition. Ich muss sagen: Wenn man das 2..3 mal gemacht hat, wird das Erstellen von RegEx-Pattern geradezu ein Kinderspiel.

    Durch die Definition werden die Pattern viel besser lesbar und verständlich (vor allem wenn man später mal wieder in den Code schaut). Auch ein Plus, wenn mal jemand anders da reinguckt. Ist zwar etwas mehr Schreibaufwand, aber aufgrund des Benefits auf jeden Fall gerechtfertigt.



    Auf jeden Fall schon mal vielen Dank für euer Interesse und eure Anregungen.


    EDIT:

    Was mir beim Schreiben der Definitionen mal wieder aufgefallen ist: In AutoIt fehlt ganz klar der MultiLine-String. In Lua: [[ multiline ]], in einigen anderen Sprachen: """ multiline """, und in AutoIt wurde der FeatureRequest dazu mit wenig Interesse behandelt und abgelehnt. :thumbdown:

  • Ich habe zwei Baustellen (GetUnsusedVars, [...]

    Vielleicht mal als Ansatz:

    (?s)^\h*(?:(?:Global|Local|Dim)\h+)?(?:Static\h+)?(\$\w+\b)(?=.+\1.+$)

    Dies findet mehr oder weniger "used"-Variables.

    "unused" scheitert momentan daran, dass lookbehinds in PCRE keine Variable länge haben dürfen.

    Das führt dann dazu, dass immer das letzte Auftreten einer Variable gematcht würde (da dahinter ja nichts mehr kommt).