STARGÅTE hat geschrieben: 12.09.2022 23:14Sehr interessantes Projekt. Gerade die Schlagwörter: "Lexer" und "NFA/DFA" triggern mich gerade, da ich für meine Scriptsprache ja auch extra n eigenes RegEx-Include gebaut habe (
hier), u.a. weil das Regex von PB zu unflexibel war (keine Callbacks, keine Backreferences bei Ersetzungen usw.).
Ja, ebenfalls ein interessantes Projekt.
Die Gründe, warum ich mich dazu entschieden habe, eine eigene RegEx-Engine zu schreiben und nicht mehr die in PureBasic integrierte RegEx-Engine-Implementation für einen Lexer zu verwenden, sind:
- Sie bietet keine Funktion, immer die längste mögliche Übereinstimmung zurückzugeben. Der Trick mit einem Word Boundary "(Enumeration|EnumerationBinary)\b" funktioniert nicht immer. Zum Beispiel funktioniert er bei solchen Schlüsselwörtern nicht: "(get|get-value)\b" würde bei dem String "get-value" nur "get" zurückgeben. Klar, anstatt dem Word Boundary Trick könnte man die Schlüsselwörter der Länge nach sortieren (von lang zu kürzer) oder zu anderen Tricks greifen, aber wenn man solche Tricks nicht anwenden muss, ist es doch schöner.
- ExamineRegularExpression() keinen Parameter besitzt, um die Startposition festzulegen (Feature-Request). Meine alte Lexer-Include muss deshalb den String immer wieder am Anfang abschneiden, was ziemlich die Performance bremst. Mit einer Krücke, die maximale Token-Länge zu beschränken, konnte die Performance etwas verbessert werden.
- ExamineRegularExpression() keinen Pointer zu einem String akzeptiert und der String beim Aufrufen der Funktion immer wieder kopiert werden muss, was wieder eine Performance-Bremse ist.
- Es keine Möglichkeit gibt, mehrere reguläre Ausdrücke gleichzeitig auszuführen und später bestimmen zu können, welcher reguläre Ausdruck zu einer Übereinstimmung geführt hat. Stattdessen muss jeder reguläre Ausdruck nacheinander ausgeführt werden.
STARGÅTE hat geschrieben: 12.09.2022 23:14Beim schnellen drüber gucken habe ich mich zwar gewundert, dass du mit Maps arbeitest, die (weil sie Strings nutzten) "sehr langsam" sind. Aber beim eigentlichen Matching wird ja dann der NFA/DFA genutzt, wenn ich das richtig überblicke.
Richtig, ich verwende Maps nur beim Erstellen der NFA/DFA und die haben nur mit MapKeys der länge von einem Zeichen zu tun, daher sind sie sehr schnell. Ich verwende sie, um doppelte Einträge zu vermeiden. Zudem wäre mit Arrays der Speicherverbrauch wahrscheinlich höher, weil sie immer einen maximalen Index von $FFFF haben müssten. Zudem kann ich bei Arrays im Gegensatz zu Maps nicht einfach feststellen, welcher Array-Index wirklich Daten hat. Bei den Maps habe ich keine Einträge ohne Daten.
STARGÅTE hat geschrieben: 12.09.2022 23:14Trotzdem ist diese caseUnfold map natürlich ziemlich sperrig. Das könntest du auch mit einer binären HashTable umsetzen: Ein array mit $FFFF Feldern zu Start-Adressen wo die Anzahl der chars liegt und die chars selbst.
Nur so n Idee.
Ja, mit der caseUnfold-Map bin ich auch noch nicht zufrieden. Du meinst also ein Array($FFFF) = *charCountStartAddress und ein Memory "charCount, char1, char2, charCount, char1, charCount, ..."? Sobald ich wieder genug Zeit habe, werde ich mir das nochmal ansehen. Der Code für die caseUnfold-Map wird natürlich automatisch generiert, so kann ich einfach und schnell andere Datenstrukturen ausprobieren.
STARGÅTE hat geschrieben: 12.09.2022 23:14Ich werde dein Module mal die nächste Tage mit meinem Lexer verknüpfen und ein paar Performance und Bug-Tests machen.
Danke, bin gespannt auf dein Feedback.
STARGÅTE hat geschrieben: 12.09.2022 23:14Allerdings verwendet mein Lexer oft "nicht gefräßig" Quantitäten, also x*? oder x+?
So brauch ich nicht '[^']+' definieren, sondern einfach '.+?'
Ja, das ist bequem zu schreiben, aber oft langsamer, weil die Engine dann viel backtracken muss:
- String 'Example text' mit RegEx '.+?'

- String 'Example text' mit RegEx '[^']+'

Meine Engine ist keine Backtracking-Engine und unterstützt "nicht gefräßige" Quantitäten daher nicht.
STARGÅTE hat geschrieben: 12.09.2022 23:14Ich finde es allerdings schade, dass du gleich vorweg schreibst "Es wird also keine Unterstützung für Capturing Groups, Backreferences, Word Boundaries und Anchors geben."
Klar, die brauchst man nicht unbedingt für einen Lexer, aber wenn du eh schon dabei bist n eigenes Regex zu schreiben?
Das ist eine Einschränkung von RegEx-Engines, die intern mit einem DFA arbeiten, anstatt mit einer Backtracking-Engine. Es gibt eine Erweiterung von DFA namens
Tagged DFA, die Teile dieser Einschränkung aufhebt. Vielleicht blicke ich da auch noch durch und kann das in einer zukünftigen Version einbauen. Moderne RegEx-Engines
können einfach zu viel und sind deshalb langsam.
Edit: Erster Punkt in der Auflistung mit "oder zu anderen Tricks greifen" ergänzt.