 Okay, gut, fangen wir an. Also, mein Name ist Hauro. Ich erzähle euch heute was zu Passing Expression Grammars. Was das ist, wer die hoffentlich lernen, wenn ihr es noch nicht wisst, fangen mal an mit einer Einführung erst mal in reguläre Ausdrücke. Wer kann mit dem Begriff denn was anfangen? Fast alle, wundervoll. Wer nimmt denn reguläre Ausdrücke regelmäßig her, um zu pausen, zu scannen, zu matchen? Das sind auch ein beachtlicher Anteil. So, reguläre Ausdrücke sind eigentlich schon ziemlich alter Hut. Anfang der 70er Jahre, Ende der 60er, wurden sie erstmals von Ken Thompson beschrieben und stellen den Formalismus zu beschreibender regulärer Sprachen. Ich möchte euch heute gar nicht großartig damit langweilen, was Sprachtheorie ist. Aber wie wir eben schon durch Handzeichen gesehen haben, wir lieben Reckekse. Irgendwie sind sie immer schnell geschrieben, für verschiedene Werte von schnell. Es gibt effiziente Implementierungen dafür und es funktioniert irgendwie, um unsere Passing-Probleme zu lösen. Aber sie sind doch ziemlich beschränkt in der Ausdrucksmöglichkeit, die man damit hat. Insbesondere gibt es da diverse Probleme mit Captures. Also wenn man sich bestimmte Teile eines Strings, dem man gepasst hat, merken möchte, ein großes Problem bei den Ausdrucksmöglichkeiten ist. Naja, wie der Name schon sagt, sind reguläre Ausdrücke. Das heißt, sie können reguläre Sprachen pasen, sie können aber nicht reguläre Sprachen pasen. Das heißt, alles, was irgendwie eine Verschachtelung hat, wird damit ziemlich knifflig beziehungsweise bedingt eine andere Art von Lösungen in der Regel, dass man Programmcode um die Reckeksetrung herum hat, die sich irgendwie mit dieser Verschachtelung beschäftigen. Außerdem sind reguläre Sprachen selbst mit regulären Ausdrücken relativ schwer auszudrücken. Es ist nur, weil eine Sprache per se regulär ist, bedeutet es eben im Umkehrschluss nicht, dass sie mit einer Reckekse einfach darzustellen ist. Ein Beispiel dafür sind zum Beispiel Komplimente. Also Komplimente von regulären Sprachen sind auch reguläre Sprachen. Das heißt, wir können sozusagen das nicht prinzipiell immer mit einer Reckeks Ausdrücken. Leider funktioniert das meistens nicht so dolle. Also ein typisches Problem, was man kennt, sind eben 10 c-ähnliche Kommentare. Also man möchte zwischen einem Slash Sternchen und einem Sternchen Slash möglichst alles erwichen, denn das ist ein Kommentar, das brauchen wir nicht. Dieses alles, was kein Sternchen Slash ist, ist in einer Reckeks aber relativ schwierig auszudrücken. Das gleiche gilt zum Beispiel für Identifier, zum Beispiel in C. Das heißt, wir können relativ einfach mit einer Reckeks definieren, wie so ein Identifier aussieht. In der Regel sind das eben eine Folge von Buchstaben in Zahlen unterstrichen. Allerdings matchen die Keywords sozusagen auch als Identifier. Sind aber im Grunde in der Menge der Identifier nicht verfügbar. Ich kann keine Variable mit dem Namen Long anlegen, weil dieses Wort bereits reserviert ist. Das heißt, ich brauche im Grunde eine Möglichkeit auszudrücken, dass es eben Identifier ohne die Wörter in Long und so weiter sind. Prinzipiell ist alles schwierig, was eben irgendwie ein String ist, der ein bestimmtes Wort nicht enthält. Das darf Versagen halt reguläre Ausdrücke häufig. Außerdem immer, wenn dann Captures oder eine Form von Grouping, also Subausdrücke vorhanden sind, wird das Ganze in drin sich nicht deterministisch. Das heißt, wir haben keine Garantie mehr für das Laufzeitverhalten und es kommt häufig zu Backtracking. Das heißt, die Engine, die das Parsing übernimmt, muss halt bereits gesehenes wieder zurücklaufen und Alternativen ausprobieren. Genau, ein Problem, was damit einherkommt, ist eben die sogenannte Long as Matching Regel. Das heißt, in den meisten Implementierungen für reguläre Ausdrücke wird standardmäßig versucht immer den größten Subausdruck zu matchen. Das hat aber ein paar Nachteile. Zum einen ist die Assoziativität bei der Zusammenfügung gebrochen. Also ich habe hier ein Beispiel gemacht. Also ich nenne immer das Ding, in dem ich suche das Subject. Das ist der Ausdruck, der für Grammatik normalerweise verwendet wird. Das heißt, wenn wir uns den String nehmen, abc.de und nach zwei verschiedenen Ausdrücken pasen, hier ist einmal quasi die eine Klammer um die ersten beiden Teilausdrücke. Dann bekomme ich für diesen String die Zerlegung a, bdc.de als einer und den leeren String raus. Wenn ich die Klammer um die letzten beiden Ausdrücke mache, bekomme ich auf einmal andere Strings raus. Also der erste Teilstring wird eben als ab gematched. Der zweite als CD, der letzte als e. Das heißt, die Art und Weise, wie ich Klammer kann für die Captures eine gewisse Relevanz haben, das liegt eben an der Regel, dass immer der längste Substring gematched werden muss. Außerdem bricht das eben die Linearität im Algorithmus bei der Suche. Dadurch das Backtracking involviert ist. Noch mal konkreter. Wenn ich das letzte Vorkommen suche, das drückt sich halt in regulären Ausdrücken mit Punktzlech und Fu aus, das würde halt das letzte Vorkommen von Fu innerhalb eines Subtracks finden, muss das ganze Ding vollständig gelesen werden. Das heißt, die Reckax-Engine muss die komplette Eingabe konsumieren, was prinzipiell nicht unbedingt ein Problem ist, wenn diese Eingaben kurz sind. Wenn ich allerdings mehrere Gigabyte vor mir habe, dann wird das richtig ätzend. Eben hier noch mal beispielhaft dargestellt, wenn ich eben das letzte Vorkommen von b in diesem String suche, der mit einem a beginnt, ein b enthält und 20 weitere a's ist die Reckax-Engine quasi genötigt bis zu diesem a zu schauen, um dann durch Backtracking nachher wieder festzustellen, dass hier bereits Schluss gewesen wäre. Das kann dann im schlimmsten Fall zu quadratischem Laufzeitverhalten führen. Und dieses quadratische Laufzeitverhalten ist in der Praxis ein Problem. Es ist halt sicherheitsrelevant mit einer neuen Klasse, die nennt sich Redos, also Regular Expression, Denial of Service, wo man gezielt reguläre Ausdrücke durch Eingaben dazu nutzen kann, Web Server downzubringen, indem sie sich halt ellenlang mit Pasen aufhalten, wo sie eigentlich nicht pasen müssten. So geschehen ungefähr letztes Jahr, als Stack Overflow für etwas mehr als eine halbe Stunde down war, genau aufgrund dieses Problems. Wir haben die halbe Stunde irgendwie überstanden, aber wir würden gerne sowas in der Zukunft nicht mehr sehen. Das Problem da war eben auch, dass ein Post abgesetzt wurde, der einfach 20.000 Leerzeichen enthielt und die Reckax sich daran verschluckt hat. So, jetzt kann man sagen, okay, aber Regular Ausdrücke, Reckaxes, das ist auch irgendwie nicht so ganz das gleiche. Also mit Regular Ausdrücke meine ich im Grunde den Formalismus, so wie in den Schulbüchern steht. Reckaxen sind für mich die konkreten Implementierungen, also Tools, die Regular Ausdrücke verarbeiten können. Und diese Implementierung setzen Regular Ausdrücke eben nicht konsequent um. Und das ist in der Praxis dann doch wieder ein Vorteil, denn man hat einige Erweiterungen eingebaut, die eben verhindern können, dass solche Sachen passieren oder mir wieder eine gewisse Garantie über die Laufzeit geben. Zum Beispiel Prozesse von Leerze Repetition, das werde ich später nochmal ansprechen. Man kann eben den Look ahead oder look behind durchführen. Das heißt, ich kann innerhalb der Reckax vorwärts schauen oder nach hinten schauen, um halt Entscheidungen über den Weg zu treffen, in dem gematched wird oder eben sogar ein Back Referencing machen, also mich auf Dinge beziehen, die die Reckax Engine bereits gesehen hat. Das ist ziemlich mächtig, das sind aber, wie gesagt, alles Erweiterungen. Das ist nicht im Formalismus der Regular Ausdrücke per se drin. Implementierungen können das tun, tun das häufig, aber es gibt keinen Formalismus. Das heißt, wir haben im Grunde für Regular Ausdrücke keine klar oder formal definierte Semantik. Wir haben keinen klar definiertes Performance Modell. Es gibt halt eben Atokoptimierung, aber wenn man jetzt wirklich seine Reckaxe auf Geschwindigkeit tremmen will, ist man im Grunde dazu verdammt zu versuchen und zu schauen, wie schnell läuft das Ding denn und daraufhin Alternativen zu probieren. In der Regel lässt das Ergebnis aber auch nicht wirklich Rückschlüsse darüber zu, warum es jetzt konkret schneller ist. Das heißt, das ist so ein bisschen Versuch um der Tum. Im Großen und Ganzen sind sie halt sinnvoll, sie sind aber in meiner Augen nur für bestimmte Aufgaben sinnvoll, insbesondere dann, wenn Eingaben klein sind und wenn die Eingaben, die ich bekomme, mehr der Wege dem bereits entsprechen, was ich erwarte. Sobald man Abwachung hat, hat man gegebenenfalls ein Problem. Außerdem ist Verschachtelung eben nicht unterstützt, was allerdings beim Pausen vieler insbesondere gut strukturierter Sprachen oder künstlicher Sprachen häufig um der Fall ist. Das heißt, muss irgendwie besser gehen. Und ein Ansatz, den ich mir daraufhin mal angeschaut habe, war eben Pausen-Explosions-Grammas. So Geschichte, die sind ungefähr genauso alt wie Reck-Echse bereits. Zwei der Haupteigenschaften von PEGs, auf die ich später noch eingehen werde, sind eben Restricted Backtracking und das Nordpredikat. Die wurden erstmals 1970 beschrieben in der Sprachtheorie. In 72 wurden dann PEGs wirklich tiefer beschrieben als Top-Down-Passing Language und dann halt generalisiert zu einer Generalized Top-Down-Passing Language. Das heißt, PEGs sind equivalent mit Top-Down-Passing Languages. Das heißt, alles, was ich in einem Top-Down-Parser bauen kann, ich auch mit einer PEG bauen und umgekehrt. Dann lag das Ganze irgendwie 30 Jahre still bis Brian Ford 2002 seine Master Thesis über PEGs geschrieben hat. Und das bemerkenswerte an dieser Thesis ist letzten Endes, dass er eine Standard-Nutation eingeführt hat für PEGs. Das heißt, wir können im Grunde jetzt einheitlich PEGs darstellen. Er hat ein PEG-Pasa oder eine PEG-Engine namens Pack-Ride geschrieben, auf die ich nicht eingehen möchte und die Vereinheitlichung von Scanning und Passing durchgeführt. Das heißt, innerhalb einer PEG sind diese zwei Vorgänge nicht prinzipiell getrennt, sondern ich habe sozusagen immer einen Passing Schritt, über den ich aber auch suchen kann, also Matchen suchen. Das ist in der PEG letzten Endes alles eins. Wie muss man sich jetzt irgendwie PEGs vorstellen? Letzten Endes ist das nur noch formale Sprache, wie reguläre Ausdrücke auch, bzw. eine Grammatik. Und sie haben starke Ähnlichkeiten zu kontextfreien Grammatiken. Kontextfreien Grammatiken sind etwas mächtiger als reguläre Ausdrücke. Das heißt, PEGs haben aufgrund der Ähnlichkeit auch mehr Möglichkeiten, Dinge auszudrücken als reguläre Ausdrücke. Man kann sich das so ein bisschen vorstellen wie eine Mischung aus einer kontextfreien Grammatik und Pattern Matching Elementen. Es gibt zwischen meinen Systemen oder den beiden Grammatiken Unterschiede in der Semantik. Zum Beispiel, dass PEGs immer eindeutig sind. Also kontextfreien Grammatiken ebenso wie reguläre Ausdrücke, sobald ich eine Auswahl habe zwischen verschiedenen Möglichkeiten ist im regulären Ausdruck und auch in der kontextfreien Grammatik nicht definiert, welche diese Alternativen zuerst genommen wird. In einer PEG ist das definiert. Der Fokus bei PEGs ist effektiv auf einer Stringerkennung kontextfreier Grammatiken und reguläre Ausdrücken. Wir haben halt, wie gesagt, die Wurzeln in der Sprachtheorie und sind eigentlich eher dazu da, eine Stringgenerierung zu beschreiben. Das heißt, die Möglichkeiten aller Sprachen oder alle Strings, die ich mit einer Sprache generieren kann, wohin gegen letzten Endes die PEGs wirklich nur für die Erkennung da sind. Im Großen und Ganzen beschreiben Sie sozusagen ein Algorithmus zur Stringerkennung und warum das für eine Implementierung wichtig ist. Darauf gehe ich dann später nochmal ein. Gut, wo eignen Sie sich? Im Grunde, da, wo sich reguläre Ausdrücke auch eignen, also für gut strukturierte Sprachen, damit meine ich jetzt überwiegend Programmiersprachen. Also alles, das, was vom Menschen mal erfunden wurde, domain-specific languages und natürlich auch künstliche Sprachen, also losspannen wir da zum Beispiel so ein Kandidat, der eine gewisse Regelmäßigkeit aufheist. Gut, wie sieht so was aus? Also der Formalismus ist ähnlich wie in anderen Sprachen auch. Also wir haben einen Satz von Regeln. Die Buchstaben, die hier sozusagen stehen, die nennen sich eben non-Terminale. Das sind also die Teile, die noch nicht bestimmt sind. Der Pfeil zeigt im Grunde immer in Richtung der Definition. Das heißt, die Regel A sozusagen hat die Möglichkeit eben gematcht zu werden über die Regel B, gefolgt von der Regel C, gefolgt von der Regel D oder von der Regel E und nicht der Regel F oder irgendwas anderes. Für die Regel B habe ich einen Terminal angefügt, also Terminale sind in einfachen Hochkommentar dargestellt und die Matchen immer exakt. Also die Regel B würde quasi jetzt den String FU matchen. Genau. Es müssen halt logischerweise immer alle non-Terminale in der Grammatik definiert sein, damit da was sinnvolles bei rauskommt. Ich habe das jetzt hier ausgründen, der Übersicht ein bisschen hergelassen. Ist das jedem so weit klar, wie das zu lesen ist? Genau. Also die Frage war, dass nicht F oder was das nicht F bedeutet, also das bedeutet eben alles, was nicht dieser Regel entspricht. Da sieht man schon, es gibt ein paar Unterschiede. Also wir haben ein Prädikat. Das erkläre ich aber gleich noch. Okay. So die drei Kernkonzepte in Passing und Expression Grammas sind eben Order Choice, Restricted Backtracking und Prädicates. Order Choice. Bedeutet effektiv einfach nur, dass die Alternativen in der Reihenfolge ihre Definitionen ausgewertet werden. Das ist jetzt keine Konvention, die Implementierung einfach so machen, sondern ist es Teil des Formalismus. PEG Implementierung haben das so zu tun und deswegen wurde da jetzt auch nicht der übliche das Pipesymbol für die Alternativen verwendet, wie das in regulären Ausdrücken der Fall ist, sondern bewusst der Slash, weil das was anderes ist. In dem Beispiel habe ich eben eine Regel A. Die matcht eben erst mal die Regel A1. Falls diese Regel nicht gematched werden kann, matcht es eben die Regel A2. Falls diese auch nicht gematched werden kann, matches Regel A3 und so weiter. Das heißt, die werden konsequent nachher landendurchprobiert. Sobald eine dieser Regeln matcht, ist die Regel A erfüllt und die ist damit fix. Das heißt, wenn A2 sozusagen matcht, wird auch auf unter keinen Umständen A3 mehr probiert, wurde A1, also A1 wurde bereits erfolglos probiert. A2 wird sozusagen gematched und behalten. A3 würde dann gar nicht mehr probiert werden. Das ist wohl der erste Unterschied zu Reglex, an dem man sich so ein bisschen gewinnen muss, wenn man diese Grammatiken liest. Restricted Backtracking bedeutet, dass im Fall eines Passing-Fielschlag sozusagen nicht beliebig weit zurückgegangen werden kann. In dem Fall haben wir eben eine Regel S, die aus den Regeln A gefockt von B besteht. Die Regel A kann jetzt gematched werden über 1 oder 2 oder beliebige Weiterausdrücke. Das heißt, sobald sich eine Alternative gefunden haben, also sei es A1 oder A2 oder eine beliebige andere, sodass A definiert ist, wird auch beim Fehlschlagen von B keine neue Alternative gesucht. Das heißt, hat A2 gematched und B schlägt Fehl, wird A so behandelt, als hätte da immer A2 gematched. Das ist eben mit Restricted Backtracking gemeint. Das heißt, eine Regel Backtrackt effektiv immer nur in sich selbst sozusagen, aber niemals in eine andere Regel hinein. Das dritte Element sind im Predicate oder Predicate. Da gibt es zwei Stück. Das eine ist das Not-Predicate. Das haben wir eben Beispiel schon kurz gesehen. Das ist immer dann erfolgreich, wenn A Fehl schlägt. Das heißt, es wird effektiv versucht, A zu pasen. Und wenn das nicht klappt, ist das wunderbar. Dann haben wir sozusagen ein Match. Und es gibt das Un-Predicate. Das ist effektiv nur synthaktischer Zucker für zweimal das Nicht-Predicate. Jetzt sagt man sich, okay, also das bedeutet ja, dass es erfolgreich ist, wenn erfolgreich gepasst werden könnte. Warum braucht man das? Man hat ja dann auch schon. Der Unterschied ist eben, dass diese beiden das Subtrack nicht konsumieren. Also das heißt, der sozusagen Lesekopf auf dem Spring wandert nicht weiter. Das bedeutet in der konsequenz, dass ich einen Look gehabt habe. Das heißt, mit dem Un-Predicate kann ich im Grunde quasi erfolgreich vorausschauen, sozusagen, ohne dass es dann weitergeht. Auch hier nochmal Predicate sind bereits Teil des Formalismus. Das heißt, das ist auch keine Erweiterung oder nichts Implementierung Spezifisches, sondern das ist Teil der Passing Expression Grammas per Sie. So, da kann man jetzt interessante Sachen mitmachen. Zum Beispiel habe ich hier mal eine Grammatik aufgeschrieben, die passt Subjects mit einer geraden Anzahl von 1 und 0. Diese Grammatik könnte man im Grunde auch als Final State Automata darstellen, also umfiniten Zustandsautomaten. Die Grafik habe ich jetzt leider nicht aufgezeichnet. Das ist folgendermaßen zu lesen. Wir sagen, wir haben eine gerade Anzahl von 0 und eine gerade Anzahl von 1, dann, wenn es mit einer 0 losgeht und einer ungraden, also Ott, ungraden Anzahl 0 und einer gerade Anzahl 1 ist oder mit einer 1 und einer gerade Anzahl 0 und einer ungraden Anzahl 1. In dieses Konstrukt bedeutet quasi das Ende des Strings. Also, wenn ich nicht irgendwas matche, dann matche ich immer das Ende. Es kann da nichts anderes matchen. Und die anderen Regeln sind eben genau das, also Ott even bedeutet eben eine ungrade Anzahl 0, eine gerade Anzahl 1 und so weiter. Das heißt, wenn man das mal logisch durchgeht, es ist effektiv genau das, dass ich zähle gerade, ob sowohl die Einsen als auch die Nullen eine gerade Anzahl haben in diesem String. Man sieht hier auch, dass das eine rechtslinäre Grammatik ist. Also rechtslinäre Grammatik bedeutet, dass in jeder dieser Alternativen das Nicht-Terminal immer auf der rechten Seite steht und nie mehr als zwei Nicht-Terminale aufeinander folgen. Das macht das Ganze zu einer nicht rechtslinären Grammatik. Und rechtslinäre Grammatiken sind immer reguläre Grammatiken. Das heißt, wir müssen diese Grammatik auch als Regex darstellen können. Das überlasse ich euch als Hausaufgabe. Ich bin daran gescheitert. Daran sieht man aber vielleicht schon mal, dass PGs je nach Aufgabenstellung eine ganz andere Ausdruckskraft haben als reguläre Ausdrücke. Das könnten, obwohl dieses Problem im Prinzip ja mit Regex und Bösba ist. Gut. Gehen wir jetzt nochmal ein bisschen auf das Thema Matching und Suche ein. Das ist ja eigentlich das, was uns meistens umtreibt, wenn wir mit regulären Ausdrücken arbeiten. Eine Eigenheit sozusagen ist, dass PGs immer nur im Präfix Sub subjects suchen. Das heißt, wir wollen einen String erkennen und wenn der Stringer eben länger ist, als unsere Grammatik, das definiert, dann ist das völlig in Ordnung. Wenn wir jetzt eine vollständige Suche wollen, das heißt, wir haben ein definiertes Ende unserer Grammatik sozusagen. Da müssen wir das explizit hinschreiben und das haben wir eben schon kurz gesehen. Also, der Punkt matcht halt jedes Zeichen wie in einer Regex auch und nicht jedes Zeichen ist eben effektiv dann erreicht, wenn ich aus dem Subject nichts mehr konsumieren kann. Das heißt, das andere Bereich da habe. Unten ist eine kurze Grammatik aufgezeigt, die effektiv nur den String A matcht mit der Länge 1. Die wurde jetzt dazu führen, dass wenn ich den Stringer anbekehme, der 2 As enthält, die End-of-String-Regel sozusagen, also ein weiteres Zeichen konsumiert werden könnte und damit schlägt das nicht prädikatfiel. Und damit sind 2 As in dieser Grammatik kein sinnvoller Wert. Okay, beim Suchen und Menschen gibt es ja verschiedene Varianten derer 4, von denen ich nur 3 behandle, weil der letzte Fall trivial ist. Also, zum Ersten diese blind 3D repetition. Das ist auch das, was Regex processive repetition nennen. Das bedeutet, matche mir so viel wie geht, egal was danach kommt. Also, danach, der Teil egal was danach kommt, ist sozusagen das blind. Das greedy ist matche so viel wie möglich. Da gibt es in den PGs den gleichen Ausdruck, also A-Sternchen. Das ist im Grunde ziemlich ähnlich zu dem, was man aus Regex kennt. Funktioniert allerdings ein bisschen anders. Die untere Variante, die dort aufgeführt ist, ist das gleiche in Grün. Also, formal geschrieben, würde das bedeuten, wir matchen ein A gefolgt von derselben Regel oder wir matchen den Lernstring. Genau, also, diese beiden Aussagen oder Varianten sind sozusagen equivalent. Die PG-Implementierung bauen sozusagen ein A-Sternchen immer in die untere Form um, bevor sie sich zur Ausführung bringen. Das ist nur eine Konvention, das es so geschrieben wird. In einer Regex haben wir mit diesem Ausdruck das Problem, dass wir quasi immer ein Backtracking haben. Die eine oder andere, also Paul zum Beispiel, hat Möglichkeiten, eine kleinen blind greedy Repetition so zu konstruieren, dass sie auch ohne Backtracking funktioniert. Das muss man auch durch den speziellen Operator einschalten und andere Implementierung haben das eben nicht. Dann gibt es die nonblind greedy repetition. Das Ziel dieser Suche ist effektiv das letzte Vorkommen von irgendwas zu finden. Das heißt, in diesem Fall so viel ist wie möglich vor B. Die untere Variante zeigt eben den Suchausdruck, der quasi das letzte Komma in einem String findet. Das funktioniert eben auch wieder über Order Choice. Das heißt, das Order Choice ist greedy, da wir die Substitutionsregel wieder in der ersten Alternative haben. Das heißt, sie wird auch immer zuerst versucht, bevor irgendwann dann B probiert wird. Und wir wollen auf jeden Fall matchen, was danach kommt. Das macht das Ganze eben nonblind. Gut, die dritte im Wunde ist eben dann nonblind greedy. Das bedeutet eben, wir wollen so viele Dinge der Regel A matchen. Wie geht? Gefolgt von dem, was in B steht. Und wir sehen auch hier, dass wir jetzt keinen speziellen Operator dafür brauchen. Also non greedy läuft in Reckhexen. Üblicherweise auch über einen speziellen Operator. Das nennt sich dort Lazy or Reluctant Repetition. Wir können das in der PEG direkt ausdrucken. Dadurch, dass wir sozusagen das, was wir eigentlich finden wollen, in die erste Alternative ziehen. Und dann erst weitere Regeln probieren. Ich habe hier als Beispiel zum Beispiel das Parsing eines Strings aufgeschrieben von zehn ähnlichen Kommentaren, wie ich das vorher schon sagte. Das war eines dieser Komplimente, die relativ schwierig auszudrücken sind. In einer PEG können wir eben sagen matche slash Sternchen. Und dann ein Endcomment. Und die Endcomment Regel besagt, wir versuchen erst, ein Sternchen slash zu matchen. Wenn das nicht funktioniert, matchen wir irgendein Zeichen. Wir wissen ja von dem Zeichen, dass es kein Stern sein kann. Und dann probieren wir noch mal Sterns slash zu matchen. Und das funktioniert dann eben so lange. Das heißt, wir können so lange sozusagen beliebige Zeichen konsumieren, bis wir endlich das Slash Sternchen finden. Und so lassen sich halt zehn ähnliche Kommentare in einer PEG abhandeln. Gut. In der Praxis haben wir jetzt also in einer PEG eine ziemlich definierte Semantec. Es ist auch wirklich gar nicht mehr als das, was ich jetzt vorgestellt habe. Das heißt, im Grunde habt ihr jetzt schon alles an der Hand, um eine PEG zu schreiben. Wir haben ein ziemlich definiertes Performance-Modell, da wir genau wissen, wie Backtracking funktioniert, wie Alternativen gewählt werden. Das heißt, im Grunde und das ist auch Teil der Arbeit von Brian Ford kann gezeigt werden, dass jede PEG ein Subject in linearer Zeit scannen kann und das sogar ohne Backtracking. Das heißt, die lassen sich sogar intern umformulieren, dass man ohne das Backtracking auskommen kann. Das ganze Ding hat natürlich jetzt ein Haken. Es ist dann prinzipiell linear, aber die Konstante ist wahnsinnig groß. Das ist jetzt performancemäßig nicht so das, was wir haben wollen. Das ist halt wieder nur praktikabel für kleine Eingaben und dann sind wir auch nicht besser als Reckhexe. Was können wir tun? Also letzten Endes wollen wir dann doch wieder irgendwas mit Backtracking, aber irgendwas, was trotzdem einigermaßen linear ist und vielleicht sogar optimierbar. Und die clevere Idee, die andere Leute dann hatten, waren, wir bauen uns eine Passing-Maschinen. Was ist jetzt eine Passing-Maschine? Effektiv ist es eine virtuelle Maschine, die effektiv nur spezielle Instruktionen für Pattern-Matching hat. Das heißt, wir stellen unsere PEGs als Programm da. Das heißt, letzten Endes bauen wir uns ein Compiler, der PEGs in Programme kompiliert und ein Matching ist dann effektiv nur noch das Ausführen eines So-Programms auf diese virtuellen Maschine. Wenn diese Maschine in der Lage ist, ihr Ende zu erreichen, habe ich ein Match. Wenn diese Maschine während dessen stehen bleibt, matcht die Eingabe nicht. Ich brauche allerdings, wenn ich restrikte Backtracking machen möchte, ein Stack. Ich muss mir halt irgendwie merken, wo habe ich angefangen zu lesen und wohin muss ich Backtracking. Das heißt, ich muss mir irgendwie den Zeiger merken in meinem Subject und dafür benötige ich eben mein Stack. Allerdings haben wir jetzt die Möglichkeit, Backtracking quasi zu vermeiden und damit wieder in der Laufzeit gewinnen, rauszuholen. Dadurch dass es ein Programm ist, haben wir natürlich auch Möglichkeiten zu optimieren. Wir können aufgrund des Wissens, dass wir haben über dieses Programm klävere Entscheidungen bauen oder klävere Compiler bauen, die dann wegoptimieren. Ich möchte da jetzt so ein paar Beispiele zeigen, die sind jetzt so in Pseudo ist Hemmler. Wir stellen uns jetzt vor, in diese virtuellen Maschine haben wir jetzt ein Programm, das den String ABA matchen soll. Das würde eben so aussehen, dass man drei Character Instruktionen hat. Die Character Instruktionen prüft ob das Subject an dieser Stelle genau den Character hat, den man erwartet. Wenn das der Fall ist, wird halt die nächste Zuckzogengladen. Wenn das nicht der Fall ist, fällt die Maschine. Das heißt, mit diesem kurzen Programm würden wir jetzt Eingaben matchen eben auf ein A. Sehen wir ein A, gehen wir in die nächste Instruktion. Sehen wir dort ein B, gehen wir in die nächste Instruktion. Sehen wir dort ein A, gehen wir in die nächste Instruktion. Dort sehen wir das Ende. Das heißt, die Maschine kommt sauber zum Halt. Wir haben den String gematched. Ist das so weit nachvollziehbar? Gut, wenn wir jetzt eine Alternative zum Beispiel da haben, also wir wollen wieder den String ABA matchen oder ein beliebiges Zeichen. Jetzt müssen wir eine Entscheidung treffen. Das heißt, wir bekommen ein paar interessante Instruktionen dazu. Unter anderem diese Choice Instruktion in Zeile 1. Damit leiten wir quasi diese Alternative ein. Und wir müssen natürlich auch irgendwann sagen, wann die Alternative zu Ende ist. Das ist die sogenannte Commit Instruktion. Die Commit Instruktion erlaubt es uns sozusagen, eine Sprung auszuführen an eine andere Stelle. Wir laufen das jetzt erst mal durch. Die erste Instruktion ist jetzt keine reine Char. Das hat folgende Hintergrund. Da sieht man schon eine mögliche Optimierung, die man machen kann. Wir haben eine neue Instruktion eingeführt, die nennt sich Testjar. Die prüft, ob diese Eingabe eben ein A ist oder nicht. Wenn man jetzt beliebige Strings hat und gewisse Alternativen, insbesondere Terminale, dann hat man in der Regel nicht genau das Zeichen direkt vor sich, was man da gerne hätte. Das heißt, die Maschine hat eine hohe Wahrscheinlichkeit, dass sie direkt im ersten Schritt fehlt. Wenn wir bereits sozusagen dann Dinge auf den Stack gelegt haben, was relativ teuer ist im Sinne unserer Aufzeit, dann müssen wir das wieder zurückspulen, obwohl wir halt sozusagen schon wissen, dass es relativ häufig fehlen wird. Das heißt, wir führen eine neue Instruktion ein, die schaut sozusagen erst mal. Erst dann, wenn ich tatsächlich ein A hab, dann merke ich mir die Dinge, die ich auf den Stack legen wollte. Das heißt, ein Fail kostet sozusagen nichts und diese Instruktion macht halt eben genau das. Wenn sie failed, springt sie direkt an Stelle 5, wo halt ein beliebiges Zeichen gematched wird. Das ist quasi der zweite Teil der Alternative oder die zweite Alternative. Falls dort ein A steht, gehe ich in die Choice Instruktion, lege halt auf den Stack, was ich mir behalten muss, matche dann wieder B und A, so wie das im ersten Beispiel auch war. Wenn die beiden erfolgreich waren und nicht failing, dann springe ich über die oder dann wird das Commit ausgeführt sozusagen und ich springe direkt zur Instruktion. Falls B oder A failing, da es innerhalb der Choice ist, sieht man hier, wird quasi in Zeile 5 gesprungen und eben falls die zweite Alternative probiert. Falls diese matcht, kommt man dann eben auch zur Ende Instruktion und weiß, dass man eine der beiden Alternativen gematched hat. Gut, jetzt möchte ich nochmal ein Beispiel zeigen, wo wir zwei Terminale haben. Also im Grunde haben wir jetzt auch wieder eine Alternative, also eine Choice, die wir in einer bestimmten Reihenfolge abarbeiten wollen, aber ihr seht jetzt schon in diesem Programm fehlt irgendwie diese Choice Nummer. Warum ist das? Das ist eine der Optimierungen zum Beispiel. Ein PG Compiler sozusagen weiß jetzt, dass diese beiden Strings oder diese beide Alternativen, die man gegeneinander matchen möchte, sich gegenseitig ausschließen. Wenn ich quasi High Matche kann ich niemals Foo Matchen. Es gibt einfach keine Welt, in der das sinnvoll möglich ist. Das heißt effektiv wird das Programm jetzt umgebaut. Wir können uns den Stack komplett sparen. Dadurch, dass sich die beiden gegenseitig ausschließen, wir schauen erstmal finde ich ein H. Wenn ich das tue, prüfe ich eben auf den Charakter E für das High. Dann komme ich zur Jump Instruktion und die springt direkt ans Ende, denn damit habe ich High bereits fertig gematched. Sollte jetzt Testjar eben nicht funktionieren, dann springe ich halt direkt ins Highlight 3 und lande dann halt direkt in der Alternative für Foo. Das heißt, hier haben wir im Grunde ein Matching vorgenommen oder eine Alternative vorgenommen ohne überhaupt unseren Stack zu gebrauchen, weil wir ihn gar nicht brauchen. Dadurch, dass wir jetzt etwas über diese Grammatik wussten. Genau. Hier ist nochmal ein Beispiel für eine Besuche. Das ist wieder eine greedy non-blind. Das heißt, wir wollen den String ABA am Ende sozusagen matchen und dazu wird es ein bisschen komplizierter. Genau. Es gibt eine Call Instruktion, die springt für uns direkt in Zeile 2. Das heißt, da matchen wir jetzt wieder das Charakter A und die Zahlen 2 bis 6 in dem Grunde halt die, die wir in dem letzten Beispiel auch schon hatten. Und können wir das erfolgreich matchen, sind wir sozusagen durch. In dieser Sub Routine, wir springen quasi an Stelle 9, bekommen hier ein Return. Das heißt, wir springen dann hier auf diese Jump Instruktion und springen dann damit ans Ende, denn damit sind wir fertig. Hätte der String, also in einem anderen Fall nicht gematched, das hätten hier sozusagen ein Fehlschlag gehabt. Es ist halt nicht ABA, dann sind wir in der zweiten Alternative. Das heißt, wir springen hier zu Position 7. Wir matchen ein beliebiges Zeichen. Das muss eigentlich immer funktionieren. Und dann kommen wir in die Jump Instruktion, mit der springen wir halt direkt wieder an Stelle 2. Das heißt, wir rufen sozusagen S wieder wieder auf, impliziert. Und dann folgt der ganze Weg, so wie ich ihn eben schon beschrieben habe. Falls irgendetwas daran nicht matcht, geht das ganze Spiel von vorne los. Das heißt, dieses ganze Konstrukt ist effektiv eine Schleife. Keine dieser Instruktion benutzt den Stack. Das heißt, es ist auch ziemlich performant. Und ist auch letzten Endes wieder eine Optimierung, denn es wird als letztes Element diese Regel aufgerufen. Und das, was wir hier sehen, ist eigentlich Classic Tail Call Optimization. Das heißt, ich ersetze nicht den Ausdruck durch sich selbst, sondern ich springe sozusagen intern in der Schleife hin und her. Genau, welche weitere Möglichkeiten gibt es noch? Also, es gibt noch eine ganze Hand weitere Instruktionen, die natürlich dann auch implementierungsabhängig sind. Die meisten dazu sind zur Optimierung gedacht. Das Basispaper dazu schreibt, glaube ich, sieben Instruktionen rein, plus fünf Optimierungsinstruktionen. Zum Beispiel kann man die Any Instruktionen bliebig häufig aufrufen. Das heißt, man kann sagen, matche mir fünf bliebige Zeichen. Wer mich nicht interessiert, was da drin steht, kann ich sozusagen einfach den Point-Aim-Subject vorwärts bewegen, ohne überhaupt irgendwas tun zu müssen. Und das ist zum Beispiel eine davon. Und es erlaubt zum Beispiel die Möglichkeit eines Just-in-Time- Compilers. Man könnte sich jetzt überlegen, auf Grund, also sozusagen mitzuschreiben, wann man matcht und wann man fehlt. Und dann aufgrund der Eingabe, seine Kompellierung zu ändern. Das heißt, ich lerne ja die Stellen, an denen irgendetwas matcht oder denen irgendetwas fehlt. Und dadurch kann ich vielleicht ein Geschickter optimieren innerhalb der Suche, während die Suche läuft, was halt natürlich gerade für eine Suche auf sehr, sehr großen eingebraten Prinzipiell Interessantes. Gut. Worüber ich jetzt hier nicht gesprochen habe, in der Kürze der Zeit, das ist natürlich für RekEx ein ganz heißes Interessantes Thema. Sie sind Captures und Substitutions. Also meistens wollen wir uns irgendwelche Subausdrücke merken und gegebenenfalls durch irgendwas ersetzen. Da möchte ich nur mal kurz ein Beispiel zeigen, wie das aussehen kann. Das ist jetzt ein Beispiel aus der Lua-Implementierung für ein CSV-Paser. Und das ist auch wirklich alles, was das Ding tut. Was man letztendlich zurückkriegt, ist quasi eine Liste von Listen mit den entsprechenden Feldwerten. Und ich denke, ihr seid jetzt sowieso schon in der Lage das zu lesen. Also wir haben hier eine Record-Regel gefolgt von einem Zahlenumbruch und einer weiteren Record-Regel und das ja null oder mehr Male. Und das ist einfach das Symbol, Upsala, das ist das Symbol für Captures. Das heißt, ich bekomme quasi für jede Zeile wieder was zurück. Und zwar die Inhalte dieser Zeile oder die Inhalte dieser Record-Regeln. Im Record gibt es sogenannte Fields, die durch Combat-Tage-Trenz sind, mindestens eins, ansonsten liebe ich viele. Auch die bekomme ich wieder in einer Liste zurück. Und die Fields bekomme ich in zwei Geschmacksrichtungen, einmal escaped. Das bedeutet, da stehen doppelte Anfangszeichen drumherum oder in simple. Das ist dann eben ohne Quotes. Die Regel für simple ist relativ einfach. Es darf kein Komma sein, es darf kein Double Quotes sein und es darf kein Zahlenumbruch sein, ansonsten ist alles erlaubt für diesen Wert. Und diese Klammern hier bedeuten eben eine Substitution, das heißt oder ein Capture. Das heißt, das, was durch diese Regel sozusagen an der Stelle gepasst wurde, wird sich behalten und an die Engines zurückgegeben. Und für die Felder, die escaped sind, haben wir hier ein Capture und diese Tilden bedeuten, das ist eine Substitution. Das heißt, da findet innerhalb des Parsens eine Ersetzung statt. Und der Feldwert sozusagen ist entweder wir dürfen kein Double Quote sein oder wir können zwei Double Quotes sein. Wenn wir zwei Double Quotes sind, dann werden die direkt durch ein Double Quote ersetzt und das beliebig oft. Das trägt Sorge dafür, dass innerhalb eines gequoteten Strings in einem CVS, wenn da ein Double Quote drin vorkommen sollte, ist dieser normalerweise als zwei Double Quotes zu schreiben. Das heißt, hier haben wir im Grunde mit dem Capturing und dem Scanning, durch diese Substitution Rule, die hier direkt angewendet wird, auch direkt alles erlegt, was wir zurück kriegen, sind quasi die reinen Feldwerte. Entweder simple Werte oder die Werte entsprechend ohne die Quotes beziehungsweise nicht nur ohne die Quotes, sondern auch alle zwei aufeinander folgenden Double Quotes durch ein einzelnes Quote ersetzt. Das heißt, das, was wir hier zurückbekommen, ist direkt konsumierbar, weil es schon korrekt gepasst wurde. Sie seht hoffentlich, da geht eine Menge. Damit möchte ich noch kurz auf die Implementierung eingehen, die mir bekannten zumindest sind eben L-Pack für Loa, dass es auch die Implementierung, die diesen VRM-Ansatz macht, ist auch in dem Paper beschrieben, dass der Lesens Wert ist. Es gibt eine Implementierung in Rust und es gibt eine Implementierung in Clojure. Clojure ist zwar prinzipiell für die Java Virtual Machine, aber ein bisschen ekelhaft aus Java heraus zu benutzen. Deswegen habe ich angefangen, eine eigene Implementierung in Kotlin zu schreiben für die JVM mit dem neuen Gini-Pack, ist bisher noch nicht veröffentlicht. Da werde ich jetzt im Laufe dieser Veranstaltung weiter dran hacken und komme dann auf GitHub, ist definitiv noch nicht production ready. Ich weiß auch nicht, ob ich es dir immer so weit bringen werde. Das ist für mich halt ein Spaßprojekt, aber wenn ihr Lust habt, könnt ihr gerne reinschauen. Und damit komme ich dann eigentlich auch schon zum Ende. Also ich finde, PEGs sind eine wunderbare konzeptionelle Basis eben für Pattern Matching. Pattern Matching, der Ansatz über eine Vm erhält eben die Laufzeit charakteristisch weitestgehend. Also es ist dann nicht mehr ganz linear, aber anderthin linear. Und die Performance der bisherigen Implementierung ist vergleichbar mit anderen Passing-Tools, aber ohne den ganzen Aufwand sozusagen, den man damit hat beziehungsweise ein Passer-Generator hat halt auch immer den Nachteil, dass man nicht mal eben on-the-fly zur Laufzeit einen Ausdruck matchen kann, sondern man muss immer schon vorher wissen, was man matchen will. Gut, das war's von meiner Seite. Gibt's Fragen? Moment, bitte. Haben wir ein Mikro? Wo kann ich die 5 plus 2 oder was ist auch immer Instruktion der Matching-Maschine? Wo kann ich die Semantik davon nachlesen? Die sind in dem Paper des Out-Sores der Lua Implementierung beschrieben. Also wenn man nach Hellpack im Netz sucht, die Referenz müsste auch noch mal hier verlinkt sein. In den Folien gibt's in entsprechendes Paper, was die komplette Implementierung beschreibt und auch noch mal die PG-Theorie dahinter, soweit es das Paasenmetall vor allem betrifft. Also ich frage Onkel Google, Lua und Pack oder was? Genau. Danke. Bitte schön. Sechs enthält, glaube ich, auch ein Paasen-Expressions- Grammar-Parser fest eingebaut. Wunderbar. Danke schön. Hier links ist noch. Ja, auch noch mehr in Merikungen also für Pfeifen heißt das Ding wohl Pi, PEG, 2. Okay. Gibt's auch eine Implementierung. Ich hab's ja allerdings selber noch nicht großartig ausgetestet. Du hattest eben so ein Beispiel, wo man ABA am Ende matchen möchte. Gibt's dafür einen guten theoretischen Ansatz, wie man das vernünftig optimiert, weil der oder du erklärst irgendwie nochmal kurz, wie das mit dem Chaa als Instruktion genau gemeint ist. Okay, gehen wir noch mal zu. Ich glaub, genau das. Das ging ja? Ja, weil du hast jetzt, wenn du irgendwie, AB, ABA oder sowas. ABA, genau. Nee, ich meine, wenn man jetzt das Wort ABA reinwirft, dann hättest du das Test Chaa, A, würde matchen und das Chaa, B, würde dann halt fehlen, aber du könntest halt das A, was danach kommt, nicht mehr als Ende erkennen, wenn jetzt Chaa, B, fehlen würde. Dann kannst du bitte nochmal langsam fragen, ich komm gar nicht mit. Ich frage mich, wie man das, wie man die Instruktion Chaa, B am besten optimiert. Chaa, B. Ja, also was genau macht das? Das matcht nur, ob an der entsprechenden Position ein B steht. Und wenn die nicht da steht, dann fällt der Ausdruck. Aber sowas wie A, AB, A, würde dann fehlen. Sowas würde fehlen, ja. Wobei, dass ihr an Wortes was akzeptiert werden sollte. Achso, ja, A, AB, A, okay, gut, jetzt bin ich bei dir. Genau. Gut, spielen wir das mal durch. Ich hoffe, ich habe das jetzt auch richtig gemacht. Also, wenn wir jetzt das erste A matchen, dann sind wir ja sozusagen in diesem Zweig drin. Das heißt, ich habe den Choice. Jetzt käme halt das zweite A an der Instruktion, wo Chaa, B steht. Damit würde diese Instruktion fehlen schlagen. So wird klar. Und wenn die Instruktion fehlen schlägt, springe ich halt direkt an Position 7, also in die zweite Alternative rein. Ja, das heißt, dass ich konsumiere hier eine weiter, also die Eingabe sozusagen. Der Zeiger steht ja zu dem Zeitpunkt noch auf dem ersten A sozusagen, ne? Mit dem NE1 konsumiere ich das erste A. Dann komme ich hier in die Jump Position und ich fange halt wieder in der ersten Regel an. Das heißt, hier teste ich wieder auf ein A. Das wäre ja bei A, B, A, das zweite A. Das Match sozusagen, das heißt, ich matche dann ein B. Auch das steht an der richtigen Stelle. Ich matche das A. Auch das steht an der richtigen Stelle. Ich committe. Das bedeutet, mein Choice ist sozusagen vorbei. Ich weiß jetzt, dass ich die erste Alternative gematched habe. Springe an Position 9. Mit Position 9 returne ich quasi unter den Call. Bin sozusagen aus der Schleife raus. Gerade auf die Jump Position und springe dann ans Ende. Und wenn ich Ende erreiche, ist das String saubergepasst. Also A, B, A matched quasi genau so, wie wir das erwarten, indem wir das erste Mal Tested, Failed in die Any Regel springt. Aus der Any Regel geht es direkt wieder in die erste Alternative hinein und da habe ich ja den String A, B, A. So weit verständlich. Weitere Frage. Nur eine Einmerkung wer danach, also nach diesem Vortrag in Pfeifen damit rumspielen will. Ich würde mich da mal dran versuchen und suche dann mit Kämpfer. Du hast erwähnt, dass ich es da so in Time Optimierungs Möglichkeiten gebe. Was für Freiheitsgrade habe ich überhaupt zum Optimieren, wenn das Verhalten vollständig deterministisch definiert ist, was passieren soll? Du hast zum Beispiel solche Optimierung, wie ich gezeigt habe. Also zum einen du kannst zum Beispiel entscheiden, dass du eben das quasi die Choice Instruktion gar nicht benutzt, also nichts mehr auf den Stack legst, weil Dinge sich gegenseitig ausschließen. Du musst dir dann nichts merken sozusagen. Du kannst weitere Instruktionen einführen. Also eine der anderen Instruktionen zum Beispiel ist das ganze Zeichen Gruppen um über eine Instruktion gematched werden. Da kann man halt diverse Bitmasken Tricks machen, dass man halt zum Beispiel eine Instruktion hat, die also bis zu, weiß ich, 16 Charakter oder sowas halt auf einmal matchen kann aus einer Gruppe heraus oder so. Das sind halt die Sachen. Also es sind jetzt keine keine, ich sag mal, Optimierung im Großen, sondern halt im Kleinen, die das halt irgendwie schneller machen. Beantwortlich. Also nichts, was aus den Daten, die historisch anfallen, lehrend, dass immer dieser Zweig genommen wird und nicht jener, sondern einfach so ein statisches Optik. Ja, wenn du es wirklich sagen wir mal eine Sprache hast, die sehr strukturiert ist oder halt immer oder sich auf gewisse Dinge verlässt. Also zum Beispiel sozusagen das in deinem ja irgendwas zum Beispiel verbetig sortiert ist oder sowas. Dann könntest du prinzipiell dieses Wissen wieder nutzen oder halt auch Häufigheitsverteilungen. Wenn du weißt, dass ein Schlüsselwort besonders häufig vorkommt, kannst du diese Regeln gegebenenfalls vorziehen in den Alternativen. Aber ist die Reihenfolge, in der ich Schlüsselbord dem Mädchen nicht durch die Grammatik definiert, wenn ich zuerst auf Keyboard eins und dann erst auf Keyboard zwei Teste, kann ich das ja nicht umstellen, weil die Grammatiker vorschreibt. Ich muss zuerst das und dann das erst testen. Genau, die muss das effektiv erlauben. Ja, natürlich. Je generischer du wirst, desto schwerer ist es für eine Maschine das zu entscheiden. Aber prinzipiell sollte man das beim definieren der Grammatik ja schon machen. Also Grammatiken werden da auch für Menschen geschrieben. Hindernis mit dem Wissen wie diese Sprache aufgebaut ist. Und da zeigen sich Optimierungsmöglichkeiten. Ich zu dem Beispiel habe ich jetzt keine Folie leider. Aber zum Beispiel das Beispiel mit dem zähnlichen Kommentaren könntest du also haben wir einmal gesehen über diese Alternativen, dass wir sagen, wir matchen erst das Ende dieses Kommentars. Wenn nicht, gehen wir in die Alternative, konsumieren ein Zeichen und versuchen auch nochmal das Ende zu matchen. Das funktioniert prinzipiell. Man könnte es aber auch anders herum schreiben. Also man könnte sagen, wir matchen beliebig lange alles was nicht Sternchen Slash ist und dann Sternchen Slash. Diese zwei Ausdrücke sind untereinander Equivalent. Sie haben aber leicht andere Performance-Charakteristiken. Das wäre zum Beispiel aber was, was ein Compiler erkennen könnte so eine Formulierung und die dann quasi in die erste Form die ich gezeigt habe übersetzen und sozusagen die schnellere Variante bevorzugen. Weil er an der Stelle weiß, dass sie Equivalent sind. Das ist halt aber dann wie soll ich sagen man muss da schon ziemlich Eindeutigkeit haben. Also es geht nicht für beliebig komplex oder verschattelte Ausdrücke. Und zwar von mir die Frage für reguläre Ausdrücke ist ja es Schnittmengen-Problem im Allgemeinen unentscheidbar. Also eine Thuring-Maschine kann ich in endlicher Zeit entscheiden, ob zwei reguläre Ausdrücke dieselben Sachen matchen. Gilt das für PEGs auch und was heißt das für diese Optimierung wo die Maschine sozusagen entscheidet bei zwei Ausdrücke sind? Hat das du ja vorgemacht mit den Strings High und Foo wo du gesagt hattest dass die Maschine sagen kann die sind definitiv widersprüchlich und daraus optimieren kann. Aber gilt das nicht nur für sehr, sehr einfache Beispiele? Ja, das gilt für sehr, sehr einfache Beispiele. Ich weiß nicht ob es einen generellen Beweis gibt ähnlich wie es die vor reguläre Ausdrücke gibt es kann gut sein ist mir jetzt nicht bekannt. Aber natürlich ob das von einer Maschine sozusagen oder einem Coppiler entscheiden zu lassen geht das nur für relativ simple Sachen. Selbstverständlich. Gut, wenn es keine weiteren Fragen mehr gibt dann vielen Dank für eure Aufmerksamkeit und schönen Abend.