Gast
#815646
Hallo, ich habe ein Java Programm und darin einen .StackOverflowError Wie kann ich nun diesen Fehler beseitigen, wie ist das allgemeine Vorgehen wenn man selber einen Fehler suchen möchte?
|
Anzeige
|
Java .StackOverflowError
Gast
#815646
Hallo, ich habe ein Java Programm und darin einen .StackOverflowError Wie kann ich nun diesen Fehler beseitigen, wie ist das allgemeine Vorgehen wenn man selber einen Fehler suchen möchte? In deinem Fall (du bist doch der Parser-Jochen?) tippe ich mal auf eine endlose Rekursion. Starte deinen Debugger, steppe den Code durch, betrachte die Variablen in deinem Code und wie sie sich verändern, und sieh nach warum die Rekursion nicht abbricht.
Gast
#815687
endlos Rekursion oder endlos Schleife.
wenn du mit eclipse programmierst, dann könntest du die debug funktion
nutzen, sonst kannst du einfach ein paar System.out.println("stelle
xyz");
einfügen, um schnell zu lokalisieren wo der Fehler auftritt.
Gast
#815744
Der Debugger findet keinen Fehler also markiert keine Stelle, die falsch
ist. (wenn ich F11 drücke und dann auf "Play" gehe)System.out.println
habe ich an manchen Stellen eingefügt, aber der Fehler kommt weiterhin.
Beim Compilieren wird gesagt die Zeile if (!A()){ ist falsch.
Korrigiert wird abr leider nichts.
Die EBNF Regel a[A] lautet ja (funktioniert):
Gast
#815753
kann der Debugger die Fehler finden und korrigieren, oder was ist das?
Es gibt keine Bedingung, die das Programm davon abhalten würde immer die Unterfunktion A() aufzurufen. Wenn
einmal false ist bleibt's immer false - pos wird ja nie modifiziert. Geht das Ganze einfach im Einzelschrittmodus durch, dann siehst du, was da schief läuft - ist meiner Meinung nach recht offensichtlich. Gruß Kai jochen wrote:
> kann der Debugger die Fehler finden und korrigieren, oder was ist das?
Der Debugger ist ein Programm, welches die Kontrolle über
dein Programm übernimmt.
Unter der Kontrolle des Debuggers kannst du zb. eine Anweisung
deines Programmes ausführen lassen. Du kannst auch Variablen
begutachten. Du kannst Breakpoints setzen (ein Breakpoint
ist ein Punkt im Programm, an dem der Debugger wieder die
Kontrolle übernimmt und dein Programm anhält, so dass du
zb. wieder Variablen anschauen kannst) etc.
Der Debugger findet keine Fehler. Er legt sich als Schale
rund um dein Programm und ermöglicht dir, deinem Programm
life zuzuschauen, wie es ausgeführt wird, welche if-Abzweigungen
genommen werden, wie Schleifen ausgeführt werden. Er ermöglicht
dir Variablen auszulesen und so festzustellen, warum eine if-
Abzweigung genommen wurde etc.
Kurz und gut: Ein Debugger ist dein Fenster in das Programm
während das Programm läuft.
Als es noch keine Debugger gab, behalf man sich mit Ausgabeanweisungen
an strategischen Stellen, um aus der Ausgabe rückzuschließen, wie
das Programm durchlaufen wird und wie sich Variablen verändern
und dadurch wieder den Programmablauf beeinflussen. Nur sitzen
diese Ausgaben notorisch immer an den falschen Stellen :-)
Mit einem Debugger braucht man das nicht mehr, man kann dem Programm
direkt zuschauen wie es abläuft.
Gast
#815793
Ich komme mit der Bedienung von dem Debugger nicht zurecht. Wie meinst du das, wenn es einmal false ist, bleibt es false? Folgende Zeilen werden in deinem Programm ausgeführt:
Merkst was? Es wird nie auch nur ein einziges return aufgerufen, aber immer wieder in A() verzweigt.
Gast
#815858
Ja, das merk ich jetzt auch :( Wie würdest du das am geschicktesten lösen? Ich habe zwar eine Idee
Nur dann gibts wenn ich eine richtige Zeichenfolge teste ein weiteres Problem. Ausgehend von meinem ersten Post lässt sich das doch bestimmt ganz einfach korrigieren, nicht? Du bist auf dem Holzweg. Dein Problem ist, dass die EBNF A -> [A]a schon nicht zulässig ist :-) Du kannst ja mal versuchen die terminalen Anfänge der beiden 'Wege' in dieser Regel zu bestimmen. Ich weiß ehrlich gesagt nicht, was dein Programm eigentlich tun soll - aber wer globale Variablen für die Steuerung einer Rekursion in unstrukturierter Form verfasst sollte schon sehr genau wissen, was er tut... vielleicht nochmal zurück an den Schreibtisch mit dem Problem...
Gast
#815960
Also mein Ausgangsproblem war A -> Aa | a ich habe das dann, so ähnlich wie bei dem Bsp von gestern umgeformt A -> (A | nichts)a A -> [A]a Ja, hier sind die terminalen Anfänge von A bzw. a ja jeweils a, nur ich weiß nicht, wie ich das ganze weiter ausklammern kann. A einsetzen bringt auch nichts, weil wenn ich es einsetze, hab ich es im nächsten Schritt ja wieder drin. Komisch, irgendwie, hast wie macht man das hier jochen wrote: > Also mein Ausgangsproblem war > > A -> Aa | a > ich habe das dann, so ähnlich wie bei dem Bsp von gestern umgeformt > > A -> (A | nichts)a > A -> [A]a > > Ja, hier sind die terminalen Anfänge von A bzw. a ja jeweils a, nur ich > weiß nicht, wie ich das ganze weiter ausklammern kann. Gar nicht. Diese Grammatik ist so nicht umsetzbar (mit einem Zeichen Vorausschau). Da musst du mit dem schweren Geschütz ran und eine völlig neue Grammatik machen, die das Gleiche bewirkt.
Gast
#816057
@Jochen Habe leider gerade genug Zeit gehabt, um den Thread (die Threads mittlerweile) zu verfolgen. Mich würde mal interessieren, Jochen, an was für einer Schule Du da gerade bist. Gymnasium, FH, oder was genau? Was für ein Fach oder Studiengang ist das? Es hilft Dir vielleicht, wenn Du Dir, mit dem kürzesten Satz beginnend, die Sätze der Reihe nach aufschreibst. Dann wirst Du darauf kommen, das Du eine solche Syntax schon einmal in dem anderen Thread hattest, wenn auch die EBNF anders aussah. Gruss Oops
Gast
#816063
@ Karl-Heinz Buchegger Sieht irgendwie doch nach einem Kurs: "Formale Sprachen" oder "Compilerbau" aus aber da passen die Grundkenntnisse irgendwie nicht. Auch keine Ahnung von Links-Rechts-Rekursion und Look-Ahead oder EBNF als Algebra. Aber es gibt sicher ne einfache Erklärung dafür, aber so langsam werde ich neugierig. Gruss Oops @Oops > Sieht irgendwie doch nach einem Kurs: "Formale Sprachen" oder > "Compilerbau" aus aber da passen die Grundkenntnisse irgendwie nicht. Yep. > Aber es gibt sicher ne einfache Erklärung dafür, aber so langsam werde > ich neugierig. Beitrag "Re: Parser einer Grammatik" Beitrag "Re: Parser einer Grammatik" Klingt für mich entweder nach einem Quereinsteiger, der gerade dabei ist sich zu übernehmen, oder jemandem der sich bisher durchs Studium gemogelt hat. No offence to Jochen, aber so sieht das für mich aus. Schön langsam kriege ich nämlich Gewissensbisse, ob das überhaupt sinnvoll ist, das alles hier so im Detail durchzukauen, was eigentlich Stoff für 5 oder 6 Übungen am Anfang des Semesters gewesen wäre. Mit viel Glück schaffst er es durch die Klausur. Aber wie geht es dann weiter?
Gast
#816102
@ Karl-Heinz Buchegger Oops. Gewissensbisse würden hier meiner Meinung nach nicht passen. Die Profs und Tutis sind ja auch nicht dämlich. Die merken schon, ob er denn in der Lage ist zu abstrahieren oder nur ein paar Lösungswege auswendig gelernt hat. Allerdings hoffe ich auch, das er hier was daraus macht. Klar ist, das er noch Einiges nachzuholen hat. Wenn er die Übungen aus den Tutorien nochmal ganz stramm durchgeht, könnte er es vielleicht packen. Dann sind die Wochenenden allerdings gestrichen. Und in Zukunft dann die Übungen von Anfang an mitmachen. Und das Drachenbuch lesen! Gruss Oops Oops wrote:
> Und das Drachenbuch lesen!
Ui. Das ist aber heavy :-)
Vom Wirth gibt es auch eine IMHO gute Einführung in Compilerbau.
Gast
#816127
@ Karl-Heinz Buchegger >> Und das Drachenbuch lesen! >Ui. Das ist aber heavy :-) Sozusagen der Mangoldt/Knopp des Compilerbaus. Nur enger gedruckt. Ha! Aho war auch mehr für nach den Übungen gemeint :-) Aber im Ernst. Wirth finde ich auch gut. Ist nur ein bischen kurz der schmale Band. Jobst fand ich noch ganz nett. Gruss Oops
Gast
#816134
@OOps Wieso ahst du "leider" genug Zeit gehabt ? Ist doch kein Grund "leider" zu sagen ;) Bin an einer Uni und das Thema ist Klausurrelevant für die Vorlesung "praktische Informatik" im ersten Semester (Studiengang Elektrotechnik). Ich habe in einem anderen Thread eine Folie gepostet (ganz zu Anfang), in der erklärt wurde, wie ein Parser funktioniert. Viel mehr haben wir in der Vorlesung / Übung nicht dazu gesagt. Vielleicht noch 1, 2 Folien dazu, das wars. Diese ganzen Spezialfälle die ich hier anspreche, kamen nie dran, können aber in der Klausur aber durchaus drankommen. Mich würde blendend interessieren, welcher Grammatik aus einem vorherigen Post das entspricht, wärst du so freundlich und sagst mir das? Sehe ich richtig, dass man nicht sofort los programmieren kann, weil der terminale Anfang hier nicht definiert ist, der aber definiert sein muss (um festzustellen, dass innerhalb einer Produktion ein terminaler Anfang nicht doppelt vorkommt) ? Leider hat mich verwirrt, dass es hier 2 grundsätzliche Lösungsansätze gibt (explizit, implizit / rekrsiv, mit Schleifen) und ich würde mich jetzt auf den mit reiner Rekursion beschränkten. Danke!
Gast
#816135
PS doch, doch ich war von Anfang an mit dem Stoff beschäftigt, nicht dass der falsche Eindruck entsteht, ich lerne jetzt 2 Wochen im Crashkurs. Nur, damit war ich beschäftigt, zu lernen was Schleifen sind, wie man einen Taschenrechner programmiert, sowas eben. 1 Semester Informatik im gesamten Studiengang kann ganz schön anstrengend sein!
Gast
#816171
@ Jochen >Wieso hast du "leider" genug Zeit gehabt ? Die Zeit hat nur ausgereicht um, mitzulesen und nicht um zu antworten. >Bin an einer Uni und das Thema ist Klausurrelevant für die Vorlesung >"praktische Informatik" im ersten Semester (Studiengang Elektrotechnik). Aha. >Ich habe in einem anderen Thread eine Folie gepostet ... können aber in der >Klausur aber durchaus drankommen. Und da gab es keine Literaturliste dazu? Kein Tutorium? >Mich würde blendend interessieren, welcher Grammatik aus einem >vorherigen Post das entspricht Das kann ich mir vorstellen. :-) Hast Du die Sätze aufgeschrieben? Sei mir bitte nicht böse, aber es macht glaube ich keinen Sinn Dir das vorzusagen, falls das Ziel ein Verständnis sein soll. Vor allem ist Lösung trivial. >Sehe ich richtig, dass man nicht sofort los programmieren kann, weil der >terminale Anfang hier nicht definiert ist, der aber definiert sein muss >(um festzustellen, dass innerhalb einer Produktion ein terminaler >Anfang nicht doppelt vorkommt) ? Kann ich gerade nicht nachvollziehen. Welche Grammatik meinst Du? A -> Aa | a hat ja nur EIN Nicht-Terminal! >...(explizit, implizit / rekrsiv... Da stehe ich wohl gerade auf der Leitung. Habe ich was überlesen? Was meinst Du mit "explizit"? Der Terminus "Rekursiver Abstieg" ist hier ein wenig irreführend. Er bezieht sich nämlich auf die Methode der Implementierung eines Parsers allgemein und nicht zwangsweise auf die konkrete Implementierung (also den letzendlichen Programmtext des Parsers). Die muss nämlich gar keine Rekursion enthalten auch wenn sie gemäß der Methode des "rekursiven Abstiegs" programmiert ist. Die Methode sagt ja nur, das jedes Nicht-Terminal einem Funktionsaufruf entspricht. Wenn die Grammatik keine Rekursion enthält ist auch kein rekursiver Funktionsaufruf nötig. Allenfalls könnte man dem Gedanken nähertreten, das immer wieder der Teilschritt "Funktion für Nicht-Terminal implementieren/aufrufen" erfolgt; Rekursion sich hier also auf die Tatsache bezieht, das für eine verbleibenden Teilabschnitt der Grammatik die Methode wiederum angewendet wird. Im übrigen ist es aus mehreren Gründen besser die Grammatik so umzuformulieren, das aus der Rekursion eine Iteration wird. Du siehst selbst das Rekursion im Programm nicht ganz simpel ist. ("Java .StackOverflowError") Das ist auch eine beliebte Übung. Du wirst sehen, das die in vielen Fällen Rekursion durch Iteration ersetzen kannst. Gruss Oops
Gast
#816182
Zum Thema Iteration statt Rekursion (ich weiß nicht wie man hier zietiert): Wir haben ausschließelich "Recursive Descent Parser" kennengelernt, ich denke mal, wenn die Aufgabenstellung lautet so einen Parser zu programmieren, dann muss ich mit reiner Rekursion arbeiten, oder? Also ich dachte, es gibt 2 grundsätzliche Vorgehensweisen, wie ich hier verstanden habe. Ich erkläre das am besten an einem Beispiel: Grammatik: A -> aCd C -> aCd | b einmal kann man die Grammatik analysieren und dann den folgenden Code schreiben:
Oder aber, man wendet stur die Regeln an und schreibt:
A -> aCd
C -> aCd | b
[/c]
public boolean A(){
if( pos >= str.length )
return false;
if (str[pos]=='a')
pos++;
if (C(){
if (str[pos] =='d'){
pos ++;
return true;
}
}
}
return false
}
public boolean C(){
if (str[pos]=='b'){
pos++
return true;
}
if (str[pos]=='a')
pos++;
if (C(){
if (str[pos] =='d'){
pos ++;
return true;
}
}
}
}
[/c]
Also wie erklärt wurde, bei einem terminalen Symbol schaut man ob es da
ist, bei einem nicht terminalen Symbol ruft man die entsprechende
Methode auf. Wenn alles okay ist return true.
Gast
#816186
Wenn ich das Bsp von oben A -> Aa | a ich habe das dann, so ähnlich wie bei dem Bsp von gestern umgeformt A -> (A | nichts)a A -> [A]a analysieren würde, dann ist doch folgender Ansatz okay, oder:
stimmt doch, oder? M.e. wäre das in Analogie zum obigen Beispiel in meinem Post von grad eben die Lösung, wenn man die Grammatik analysiert hat. Gibt es noch eine Lösung, wo man das nicht brauch?
Gast
#816219
@Jochen >(ich weiß nicht wie man hier zitiert): Zitieren tut man mit dem mathematischen Grösser-Zeichen ">". >Wir haben ausschließelich "Recursive Descent Parser" kennengelernt, ich >denke mal, wenn die Aufgabenstellung lautet so einen Parser zu >programmieren, dann muss ich mit reiner Rekursion arbeiten, oder? Eben nicht. Schon deswegen nicht, weil nicht jede Grammatik eine Rekursion enthält. Siehe auch den langen Text am Ende von meinem Post von 20:12. Mit welchem Kopfstand würdest Du eine Rekursion in A->a b C d e. C-> e|f reinbringen? Die Methode heist "rekursiver Abstieg" und erzeugt eine Implementierung. Die Implementierung kann, muss aber keine Rekursion enthalten. Dies hängt ausschliesslich von der Grammatik ab. Es ist zwar üblich "Recursive Descent Parser" zu sagen, aber genaugenommen falsch. Es müsste heissen: "Ein durch die Methode des rekursiven Abstiegs erzeugter Parser". >Also ich dachte, es gibt 2 grundsätzliche Vorgehensweisen, wie ich hier >verstanden habe. Ich erkläre das am besten an einem Beispiel: Das müssen wir nochmal genauer besprechen. Du scheinst anzunehmen es gäbe einen Unterschied in der Vorgehensweise bzw. im Ergebnis wenn man die Grammatik analysiert und wenn man sie nicht analysiert. >einmal kann man die Grammatik analysieren und dann den folgenden Code schreiben: und >Oder aber, man wendet stur die Regeln an und schreibt: bzw. >M.e. wäre das in Analogie zum obigen Beispiel in meinem Post von grad >eben die Lösung, wenn man die Grammatik analysiert hat. Dieser Gedanke ist abwegig. (Wie kommst Du darauf?). Analyse einer Grammatik macht hier nur Sinn um herauszufinden ob eine Grammatik etwa rekursiv oder nicht-rekursiv ist, welchen Look-Ahead sie benötigt oder welche ersten terminalen Symbole die Alternativen haben. Sonst keinen! Deine Beispiele von 20:28 implementieren nicht beide einen Parser für die Grammatik: A -> aCd C -> aCd | b Der erste Parser implementiert vielmehr einen Parser für: A -> a{a}Cd{d} C -> aCd | b wobei die Funktion C nicht angegeben ist. Wenn das der Kernpunkt Deiner Beispiele ist, dann ist die Antwort nein. Mal abgesehen davon, das es keinen Sinn macht eine Grammatik zu implementieren ohne sie zu analysieren, muss, egal ob man analysiert oder nicht immer zwei Parser herauskommen, die die Menge derselben Sätze als richtig und die komplementäre Menge als falsch beurteilt. Bei deinem nachfolgenden Post von 20:33 scheint es so zu sein, das Du meinem Tip gefolgt bist. Allerdings hast Du die Sätze nicht hingeschrieben und ich hätte mit an Sicherheit grenzender Wahrscheinlichkeit ein "Ahaaaa" erwartet. Dein Code jedenfalls könnte bedeuten, das Du den Knackpunkt verstanden hast. Das hat aber nicht mit analysieren oder nicht analysieren zu tun. Schreibe mal die Sätze von: A -> Aa | a auf. Gruss Oops
Gast
#816224
Oops Ich habe was falsch gelesen. Sorry. Beide Beispiele von 20:28 implementieren die selbe Grammatik. Wobei aber nach wie vor im ersten der Code für C fehlt. WIe auch immer. Das hat nichts mit analysieren oder nicht analysieren zu tun. Gruss Oops
Gast
#816229
Das war doch mal ein Beispiel aus einem alten Thread, wo es darum ging, ob und wie man feststellen kann ob zwei Gruppen von Terminalen in gleicher Anzahl vorkommen. Oder ob es über die Formulierung der Grammatik geht oder ob man in Variablen zählen und sie vergleichen muss. Was hat das mit analsysieren zu tun? Gruss Oops
Gast
#816244
>Das müssen wir nochmal genauer besprechen. >Du scheinst anzunehmen es gäbe einen Unterschied in der Vorgehensweise >bzw. im Ergebnis wenn man die Grammatik analysiert und wenn man sie >nicht analysiert. Ja. Alles mein Eigenschluss nach den ganzen Lehveranstaltungen hier im Forum ;) z.B. bei A -> aA | b wäre mit der Analyste die Lösung
Hat man keine Ahnung wie die Grammatik aussieht, stellt man fest, dass in der Produktion ein terminaler Anfang nicht zweimal auftaucht. Man kann also mit dem Niederschreiben beginnen:
Ich dachte in dem Beispiel, das ich einführend zu diesem Thema hier gebracht habe gäbe es auch 2 solcher Möglichkeiten. Dann scheint es im Fall A -> [A]a oder A -> [A]b usw nur eine Lösung, im fall A -> a[A] , A -> b[A] aber zwei Lösungen (die beiden von diesem Post hier) zu geben. Ist das soweit korrekt? >Schreibe mal die Sätze von: >A -> Aa | a >auf. >Gruss >Oops Das ist doch a{a} während A -> a[A] gleichbedeutend mit {a} ist, oder?
Gast
#816245
Das ist doch a{a}
während
A -> a[A] gleichbedeutend mit {a}a ist, oder?
Gast
#816267
@Jochen Oh. Da muss was elementares schiefgegangen sein. Klären wir das mal Stück für Stück. >Ja. Alles mein Eigenschluss nach den ganzen Lehveranstaltungen hier im Forum OK. Wäre halt wichtig zu wissen, wie, d.h. durch welche Aussage Du darauf gekommen bist. Zuerst: >Hat man keine Ahnung wie die Grammatik aussieht... Wie meinst Du das? Wie soll man denn keine Ahnung haben wie die Grammatik aussieht? Nenne ein Beispiel. Gruss Oops
Gast
#816275
@Jochen Ich glaube, ich ahne da was. Hat das damit zu tun, das Du bei A -> Aa | a. nicht weisst, wievielmal das A rekursiv aufgerufen wird? Gruss Oops
Gast
#816282
Freundlich von dir! Die Aussage von Heinz >Der Unterschied zwischen den beiden Lösungen ist ganz einfach der: >In der ersten Lösung ergibt sich das Verhalten, daß immer gleich >viele 'a' wie 'b' enthalten sein müssen, implizit. Es ist quasi >ein Nebenprodukt. In der zweiten Lösung hat sich jemand zurückgelehnt >und die Grammatik analysiert und festgestellt, daß das so sein muß >und hat dieses Wissen explizit hineinkodiert. in Beitrag "Parser für Grammatiken" und auch die Tatsache, dass es (siehe mein Post hier um 21:21, die beiden Code Stücke sind doch gleichwertig?) 2 Lösungen für das Problem gibt, eine die man sich clever überlegen muss und deine, die man direkt hinschreiben kann. >> Wie meinst du das? Wie soll man denn keine Ahnung haben .... Naja aussieht, okay. Die Grammatik ist ja gegeben. Aber man muss ja nicht unbedingt einen Plan haben, welche Wörter durch die Grammatik erzeugt werden, bzw. welchen Sinn die Grammatik hat. Das findet man ja oftmals nur durch das Aufschreiben und herumexperimentieren raus, mich leider eher später als früher. Aber das mit den 2 Lösungn, ist doch soweit richtig, oder kannst du mir mein Missverständnis erklären ? Ich habe ja um 21:21 hier 2 Codestücke geschrieben, das sollte doch ein und die selbe Grammatik in beiden Code Stücken sein, oder? Davon unabhängig, das generelle Vorgehen ist doch: Schauen, dass in eine Produktion keine terminalen Anfänge gleich sind. Falls doch, ausklammern und dann hat man das entsprechend umgeformt. Taucht man bei der Umformung auf den Ausdruck (A | nichts) a bzw. [A]a hilft nur eine while Schleife, um diesen Ausdruck zu realisieren.
Gast
#816356
@Jochen Ahaaaa. ;-) >Der Unterschied zwischen den beiden Lösungen ist ganz einfach der: >In der ersten Lösung ergibt sich das Verhalten, daß immer gleich >viele 'a' wie 'b' enthalten sein müssen, implizit. Es ist quasi >ein Nebenprodukt. In der zweiten Lösung hat sich jemand zurückgelehnt >und die Grammatik analysiert und festgestellt, daß das so sein muß >und hat dieses Wissen explizit hineinkodiert. Da ist wirklich was schiefgegangen. Jetzt sehe ich auch das Wort "implizit". Zunächst einmal rufe Dir bitte den Kontext der Frage ins Gedächtnis. Es ging um Deine Fragestellung welcher der beiden Wege (der von mir mit den Zählern für as und bs oder der von Karl-Heinz, der "richtige" wäre. Daraufhin hat Dir Karl-Heinz erklärt, das es keinen "richtigen" Weg gibt, sondern das es wichtig ist, das die "Lösungen" der Aufgabe korrekt funktionieren. (Es gibt noch weitere Aspekte, aber auf die kommt es hier nicht an). Es ist völlig egal wie die Lösungen das machen. Wenn Du ein Eichhörnchen zum Parser erziehen kannst ist das eine korrekte Lösung. Dann hat er Dir erklärt was der Unterschied der beiden Lösungen ist. Hier hat er, ganz sicher unabsichtlich, eine Analyse nur bei der zweiten Lösung unterstellt. Das ist aber so nicht ganz richtig. Denn die Umsetzung der Grammatik erfordert immer eine systematische Untersuchung der Grammatik. Das ist eben eine Analyse. Und auch die erste Lösung erfordert eine "Analyse". Es kann also keinen Parser (egal welchen) ohne Analyse der Grammatik (egal welche) geben. Demzufolge kann es auch keinen Unterschied zwischen Parsern die mit Analyse und solchen die ohne Analyse erstellt worden sind. Die Unterscheidung macht gar keinen Sinn! Der zweite Unterschied bezieht sich auf "implizit" und "explizit". Die Bedeutung beider Worte ist Dir sicherlich bekannt. Ich möchte daher nur auf die Verwendung in diesem Kontext eingehen. Die Grammatik (aus Deinem Post Beitrag "Re: Parser für Grammatiken" ) > A -> aCd > C -> aCd | b hat die Eigenschaft, das die Anzahl der a gleich der Anzahl der d sein muss, damit die Sätze korrekt sind. Die Eigenschaft der Anzahl von Terminalen und ihrer Gleichheit ist nirgendwo ausdrücklich genannt. Es gibt ja auch keine sprachlichen Mittel für solche Ausdrücke in (dieser Form) der EBNF. Vielmehr ergibt sie sich implizit aus der Beschreibung. Wenn man nach der Methode des rekursiven Abstiegs einen Parser für diese Grammatik konstruiert, dann wird sich ein Programm ergeben. Auch in diesem Programm wird dann nirgends die Anzahl der Terminale oder deren Gleichheit genannt. Meine Lösung hat sich unmittelbar aus Deinem Post Beitrag "Re: Parser für Grammatiken" mit der Grammatik A -> aCd C -> b ergeben. Ich habe keine neue Grammatik beschrieben, sondern direkt den Programmcode des Parsers. Für den beschriebenen Parser hätte die Grammatik um den Zusatz "Die Anzahl der a muss gleich der Anzahl der d sein" ergänzt werden müssen. Genaugenommen ist dies in EBNF nicht möglich denn sie enthält keine natürlichsprachlichen Sätze. (Es gibt da inzwischen ein Änderung, aber das ist hier nicht relevant). Aber in meinem Code tauchen die Anzahl der Terminale und ihre Gleichheit ausdrücklich in Ausdrücken der Sprache C auf. Deswegen das "explizit". Die Begriffe "explizit" und "implizit" beziehen sich hier nicht auf eine Vorgehensweise bei der Analyse. Sondern vielmehr darauf, ob eine Eigenschaft der Grammatik im Programmtext des Parsers ausdrücklich genannt wird oder nicht. >Leider hat mich verwirrt, dass es hier 2 grundsätzliche Lösungsansätze >gibt (explizit, implizit / rekursiv, mit Schleifen) und ich würde mich >jetzt auf den mit reiner Rekursion beschränkten. Der Lösungsansatz heisst "Rekursiver Abstieg". Die Grammatik selbst schreibt Dir vor ob im Programmtext Rekursionen vorkommen oder nicht. Da hast Du keine Wahl. Allerdings, und das betrifft einen Teil der Rückfragen von Karl-Heinz und mir, gibt es mehrer Wege, ein und diesselbe Grammatik niederzuschreiben. Ist das soweit klar? Gruss Oops
Gast
#816374
Oops, vielen Dank für deine ausführliche Klarstellung ;) Ich bin jetzt sehr müde, und habe schon Kopfschmerzen. Dann habe ich noch einen Link entdeckt, den ich mir morgen genau ansehe: http://www.inf.fu-berlin.de/lehre/WS04/uebersetzerbau/beispiele/Linksrekursion.pdf Ich schreibe morgen nochmal meinen Fortschritt / Fragen, wenn du magst kannst du ja nochmal vorbeischauen :-) Danke, Oops, du / ihr helft mir echt wahnsinnig !
Gast
#816375
Also ich schreibe mal schon weiter. >und auch die Tatsache, dass es (siehe mein Post hier um 21:21, die beiden >Code Stücke sind doch gleichwertig?) 2 Lösungen für das Problem >gibt, eine die man sich clever überlegen muss und deine, die man direkt >hinschreiben kann. Das ist eben der Irrtum, den ich in meinem Post vorher hoffentlich jetzt aufgeklärt habe. Die Methode des "rekursiven Abstiegs" schreibt Dir genau vor, wie Du vorgehst und jeder wird auf das selbe Ergebnis kommen. (Leider ist das nicht vollständig, aber diese Sachen sind hier nicht relevant). Wenn Du Deinen Code von 21:21 wieder rückwärts in eine Grammatik übersetzt wirst Du feststellen, das der Code
Sätze der Grammatik A-> {a} b erkennt.
während der Code
Sätze der Grammatik A->aA|b erkennt. Beide Sprachen sind verschieden. Die erste umfasst die Sätze: b, ab, aab, aaab, und weitere beliebige Folgen von a gefolgt von b. Die zweite umfasst die Sätze ab, aab, und weitere beliebige Folgen von a gefolgt von b. Der Satz b aber ist in der zweiten Grammatik nicht enthalten. Ist das soweit klar? Gruss Oops
Gast
#816378
@ Jochen Ja, gut. Ich schreib schon mal weiter. Gute Besserung. Gruss Oops
Gast
#816399
@ Jochen Also gut, weiter gehts. Wenn ich mir Deine Beispiele für Code der aus einer "analysiertem" Grammatik entstanden ist, und solchem, bei dem Du stur dem Verfahren gefolgt bist, dann fällt mir folgendes auf. 1. Cleverer Code enthält while-Schleifen. 2. Dummer Code enthält Funktionsaufrufe. Teilweise haben wir das ja jetzt oben schon geklärt. Es gibt immer eine Code-Entsprechung zwischen dem EBNF-Sprachelement und einem Teil des Parsers. Das hast Du ja auch soweit gut erfasst. Du setzt while-Schleifen ein und ifs usw. Alles OK. Aber ob Du eine while-Schleife oder ein if einsetzt ist von der Grammatik vorgegeben. Da hast Du keine Wahl. Du bist auch nicht gefordert hier clever zu sein. Nicht an dieser Stelle. Sondern woanders, bei der Grammatik selbst. Die Methode sagt: Ein if bzw. while usw. für jedes terminale Zeichen. Ein Funktionsaufruf für jedes Nicht-Terminale. So geht das. Nicht anders. Du fragtest z.B. um 21:21 >A -> a[A] ... aber zwei Lösungen Ganz klar: _Nein!_ Aus der Methode des rekursiven Abstiegs ergibt sich (im Rahmen der momentanen Fragestellung) genau eine Lösung. Ist das klar soweit? OK. Und um Deine Frage nach den Umformungen zu beantworten: >A -> Aa | a Das ist doch a{a} Stimmt. Aber nicht ausschliesslich. Es gibt noch andere Ausdrücke die zu A->Aa|a und a{a} äquivalent sind. >A -> a[A] gleichbedeutend mit {a}a ist, oder? Stimmt. Aber nicht ausschliesslich. Es gibt noch andere Ausdrücke die zu A -> a[A] und {a}a äquivalent sind. Gruss Vielen Dank Oops, dass du heute abend für mich eingesprungen bist. > Hier hat er, ganz sicher unabsichtlich, eine Analyse nur bei der > zweiten Lösung unterstellt. Das ist aber so nicht ganz richtig. > Denn die Umsetzung der Grammatik erfordert immer eine systematische > Untersuchung der Grammatik. Ja klar. Gemeint war: Jemand hat sich die erste Grammatik angesehen (also analysiert), sie für gut befunden und festgestellt, dass sie direkt in ein Programm umgesetzt werden kann. Bei der zweiten hingegen ging das nicht. Nach dieser Analyse hat er weiter gemacht, den Sinn der Grammatik analysiert und eine dazu äquivalente Grammatik geschrieben, die umsetzbar ist. Der erste Teil der Analyse, der kann nach rein formalen Regeln erfolgen. Dazu muss man nicht besonders denken, sondern einfach nur relativ stur gewisse Dinge der Grammatik abprüfen. Wie zb. ob es Probleme mit den terminalen Anfängen gibt. Wenn in diesem Teil der Analyse keine Probleme auftauchen, dann kann man direkt an die Umsetzung in einen rekursiven Abstieg gehen. Bei der zweiten genannten Grammatik, ist man aber bei diesem Abklopfen der Grammatik auf Probleme gestossen. Daher kann man nicht sofort eine Umsetzung machen. Man muss weiter analysieren, was denn der Sinn der Grammatik sei. Und da kommt jetzt das 'Zurücklehnen' ins Spiel. Hat man den Sinn der Grammatik (also: was will uns der Autor sagen) erfasst, dann kann man meist eine dazu äquivalente Grammatik angeben, die sich dann auch in einem rekursiven Abstieg umsetzen lässt. So war das gemeint. Mein Fehler, wenn das nicht richtig rübergekommen ist. > Bin an einer Uni und das Thema ist Klausurrelevant für die > Vorlesung "praktische Informatik" im ersten Semester (Studiengang > Elektrotechnik). Muss mich entschuldigen, da hab ich dir was unterstellt. Aber im Ernst: Euer Prof hat schon einen Hieb. Das Thema Compilerbau ist Vorlesungsstoff im Informatik-Studium im 4. Semester. Und da gehts ein ganzes Semester nur so dahin. Sowas in einem Einführungskurs im ersten Semester zu bringen ist sträflicher Leichtsinn und führt zu nichts. > Aber ob Du eine while-Schleife oder ein if einsetzt ist von der > Grammatik vorgegeben. Da hast Du keine Wahl. Du bist auch nicht > gefordert hier clever zu sein. Nicht an dieser Stelle. Sondern > woanders, bei der Grammatik selbst. Ich denke, das hier ist die wichtigste Aussage überhaupt. In einem gewissen Sinne sind EBNF und Java (oder C-Programm) nur andere Darstellungen desselben Sachverhaltes. Das Programm folgt mechanisch automatisch aus der EBNF. Wenn man sich also sicher ist, dass man bei der Übertragung der EBNF in das Programm keinen Fehler gemacht hat (der häufigste Fehler ist ein vergessenes Weiterschalten zum nächsten Symbol nachdem ein terminales Symbol erkannt wurde), und der Parser funktioniert trotzdem nicht, dann muss man den Fehler immer in der EBNF Grammatik suchen. Dort hat man dann irgendetwas übersehen. Wenn die Grammatik verkorkst ist, dann wird es auch der Parser sein. Es ist aber grundfalsch das Problem lösen zu wollen, indem man am Parser rumbastelt. Da hilft nur eins: Grammatik her und die Grammatik ändern.
Gast
#816556
>Wenn Du Deinen Code von 21:21 wieder rückwärts in eine Grammatik >übersetzt wirst Du feststellen, das der Code > > [...] > >Sätze der Grammatik A-> {a} b erkennt. >während der Code > >[...] > >Sätze der Grammatik A->aA|b erkennt. > >Beide Sprachen sind verschieden. Insbesondere dieser Teil hat sehr dazu beigetragen, dass ich es nun weitestgehend hoffe, verstanden zu haben. Ich war davon ausgegangen, dass A-> {a} b = aA|b ist. Aber das ist ja dann nicht der Fall, wie du sagst. Ich war nur nach folgendem Beitrag davon ausgegangen, den ich dann irgendwie Missverstanden haben muss: ------------------------------------------------------------------------ --------------------- Autor: Karl heinz Buchegger (kbuchegg) (Moderator) Datum: 27.02.2008 11:16 jochen wrote: > funktioniert er. Ich habe versucht, ihn umzuschreiben für die folgende > Grammatik > > B-> aB > B-> b Du musst diese Grammatik noch mal umschreiben. Diese hier eignet sich nicht für einen rekursiven Abstieg. Insbesondere kann es nicht 2 Regeln für 'B' geben. Für jedes nicht terminale Symbol kann es nur 1 Regel geben. B -> { a } b. (Heist soviel wie: Ein B ist eine (möglicherweise auch 0 lange) Sequenz von a gefolgt von einem b. ------------------------------------------------------------------------ --------------------- Ich habe mir jetzt in dem Zusammenhang mit dem Bsp. A-> Aa | b, was ja in abgewandelter Weise das Ausgangsproblem war, mal das Thema Linksrekursionen angeschaut. demzufolge kann man die Grammatik umschreiben in A -> bA' A' -> a A' | epsilon und diese hab ich dann direkt in Code verfasse (im folgenden Code steht B für A' in der genannten Grammatik)
und das schöne ist, es funktioniert auch ;) Nochmal an dieser Stelle vielen Dank, ich werde auf jeden Fall von der Klausur berichten, aber vielleicht gibts ja zu meinem Beitrag von Euch noch Hinweise ;) jochen wrote: > Ich war davon ausgegangen, dass A-> {a} b = aA|b ist. > Aber das ist ja dann nicht der Fall, wie du sagst. Moment. Wie ist hier die Klammernsetzung? ist A -> aA|b als A -> a ( A | b ) zu lesen, oder ist es als A -> ( aA ) | b zu lesen? Beide Sprachen sind wiederrum verschieden, und die zweite Form ist äquivalent zu A -> { a } b Üblicherweise wird A -> aA | b als A -> ( aA ) | b gelesen. D.h. direkt aneinanderstehende Symbole binden stärker als | > > Ich habe mir jetzt in dem Zusammenhang mit dem Bsp. > A-> Aa | b, was ja in abgewandelter Weise das Ausgangsproblem war, mal > das Thema Linksrekursionen angeschaut. > demzufolge kann man die Grammatik umschreiben in > > A -> bA' > A' -> a A' | epsilon Das kann nicht stimmen. In deiner Urgrammatik: A -> Aa | b kann nach einem b nichts mehr kommen. Wenn b auftaucht ist der Satz zuende. In deiner neuen Grammatik muss aber nach einem b noch ein A' kommen. Ein A' kann zb ein einzelnes a sein. D.h. der Satz ba ist zwar Satz deiner zweiten Grammatik aber nicht Satz deiner ersten Grammatik.
Gast
#816622
> Wenn Du Deinen Code von 21:21 wieder rückwärts in eine Grammatik > übersetzt wirst Du feststellen, das der Code > >[...] >Sätze der Grammatik A-> {a} b erkennt. >während der Code >[...] >Sätze der Grammatik A->aA|b erkennt. >Beide Sprachen sind verschieden. ------------------ >Moment. >Wie ist hier die Klammernsetzung? >ist A -> aA|b >als >A -> a ( A | b ) >zu lesen, oder ist es als >A -> ( aA ) | b >zu lesen? >Beide Sprachen sind wiederrum verschieden, und die zweite >Form ist äquivalent zu >A -> { a } b -------------------- Wenn Oops sagt, A-> {a} b und A->aA|b sind verschieden, Heinz aber meint A -> ( aA ) | b ist äquivalent zu A -> { a } b, wie habe ich das zu verstehen? Wenn A -> Aa | b nicht gleich A -> bA' A' -> a A' | epsilon ist, was habe ich dann bei der Umwandlung falsch gemacht? Die Regel, nach der umgwandelt wird, habe ich im Anhang. jochen wrote: > Wenn Oops sagt, A-> {a} b und A->aA|b sind verschieden, > Heinz aber meint A -> ( aA ) | b ist äquivalent zu A -> { a } b, wie > habe ich das zu verstehen? Das es darauf ankommt, wie aA|b gelesen wird. Im Zweifelsfall (wie in der Mathematik) immer Klammern setzen! jochen wrote: > Wenn > A -> Aa | b > > nicht gleich > > A -> bA' > A' -> a A' | epsilon > > ist, was habe ich dann bei der Umwandlung falsch gemacht? > Die Regel, nach der umgwandelt wird, habe ich im Anhang. Diesmal hab ich mich ins Boxhorn jagen lassen :-) ba ist sehr wohl Satz der ersten Grammatik b a | | v | A | | | +-+--+ | v A und damit ist mein Einwand hinfällig. (Sch..ß Linksrekursion)
Gast
#816647
Okay, vielen Dank, ich weiß garnicht wie sehr ich zum Ausdruck brigen kann wie ich mich über Eure Hilfe hier freue. Werd noch etwas rumspielen, weitere Parser schreiben usw ...
Gast
#816808
@ Karl-Heinz Buchegger >Aber im Ernst: Euer Prof hat schon einen Hieb. Das Thema >Compilerbau ist Vorlesungsstoff im Informatik-Studium im 4. Semester. >Und da gehts ein ganzes Semester nur so dahin. Sowas in einem >Einführungskurs im ersten Semester zu bringen ist sträflicher >Leichtsinn und führt zu nichts. Full Ack. Teeren und federn den Mann. Und dann im Winter auf der Terasse einen Ada-Compiler schreiben lassen. @Jochen: Falls möglich würde ich gerne mal die ganzen Folien sehen. Gruss Oops
Gast
#816841
@Jochen >Wenn Oops sagt, A-> {a} b und A->aA|b sind verschieden, dann hat er da was Falsches gesagt, und wenn >Heinz aber meint A -> ( aA ) | b ist äquivalent zu A -> { a } b und vor allem auch A -> aA|b = A ->(aA)|b dann hat er da was gesagt, was Oops so auch hätte sagen (s|w)ollen. Und das wird jetzt in aller Ewigkkeit in irgendeinem Speicher sein. ;-) Gruss Oops
Gast
#816862
OOps, gerne schicke ich dir die gesamten Folien. Kannst du mir deine Mail adresse zukommen lassen ?
Gast
#816875
@Jochen Dachte eher, das man sie auf der Uni-Seite irgendwo beim Vorlesungsmaterial anschauen kann. Danke. Gruss Oops
Gast
#816911
Da muss man eingeloggt zu sein. Ich lads grad hoch. Bist du so ca. in 20 Min hier, dann kannst du es runterladen, ich möchts nur so lange drin haben, wie du es dir lädst. Kannst mir ja bescheid geben, wenn du grad hier bist ;)
Gast
#817178
@Jochen Oh. Das wäre nett gewesen. Leider musste ich noch was arbeiten. Naja. Ist nicht so schlimm. Gruss Oops
Gast
#817185
Kannst du mir eine mail an hardice at lycos.de schicken? Dann geb ich dir den Link davon Antwort schreibenBitte melde dich an, um einen Beitrag zu schreiben. |
Anzeige
|