• Keine Ergebnisse gefunden

Die Erl¨auterung der Implementierung beginnt bei dem letzten Schritt, der Codeausf¨uhrung. Um die vorherigen Schritte besser zu verstehen, ist es n¨amlich n¨utzlich, erst einmal den Zwischencode und seine Verwendung verstanden zu haben. Nachdem der Zwischencode und seine Ausf¨uhrung bekannt sind, wird seine Erzeugung (d.h. Schritte 1, 2 und 4) im n¨achsten Abschnitt Parser erkl¨art.

5.2.1 Zwischencode

Die meisten Anwendungsregeln der operationalen Semantik m¨ussen Unterausdr¨ucke ausf¨uhren, um angewendet zu werden. Es liegt also nahe, den Code als einen Baum zu strukturieren und diesen in einer Postorder ¨ahnlichen Reihenfolge abzuarbeiten.

Bei der Implementierung werden mehrere Anwendungsregeln zu einem Knoten zusammengefasst.

Eine solche Gruppierung von Regeln wird durch eineC++Klasse repr¨asentiert.

Auswertungsregeln Klasse

Referenz ReferenceNode

Regeln, die sich mite.v befassen:

RZObj,Attribut, AttributDekl,FunAttributDekl DotNode Regeln, die sich mit Zuweisungen befassen:

KZuweisung,RZuweisung AssignNode

Regeln, die sich mitv befassen:

VarDekl, Var VariableNode

Regeln, die sich mitlocal vbefassen:

LokalVarDekl, RZLokal LocalNode

Regeln, die sich mit $v befassen:

GlobalVarDekl, GlobalVar, RZGlobal GlobalNode Regeln, die sich mit Funktionen befassen:

FErzeugung FunNode

Regeln, die sich mitSP ACE Ausdr¨ucken befassen:

FVerz¨ogertA-B,FVerk¨urzt SpaceNode Regeln, die sich mit dem Funktionssaufruf befassen:

FAusf¨uhrung, FVerzweigungA, FVerzweigungB CallNode

Auswertungsregeln Klasse Regeln, die sich mit Zeilen befassen:

FAufImplizitA - FAufImplizitD, Mulitline LineNode Regeln, die sich mite:v befassen:

MethodeAusw, MethodeDekl ColonNode

Yield YieldNode

Return ReturnNode

Regeln, die sich mit der While Schleife befassen:

WhileEnd, WhileSchleife WhileNode

Die fehlenden Regeln, wie z.B. f¨ur die Auswertung vondef fun(x) ... endwerden bereits bei der Zwischencodeerzeugung abgearbeitet und direkt zufun := do catch x -> ... end umge-wandelt.

Des Weiteren ist die Zuordnung f¨ur die KlassenFunNodeund CallNodenicht ganz eindeutig, da erstere allgemein Funktionen repr¨asentiert. Bei dem Aufruf einer Funktion wird die Funktion ausgef¨uhrt, das heißt die Ausf¨uhrung einesCallNode, wird also auch dieFunNodeausgef¨uhrt.

Die Arbeitsweise der Implementierung wird Anhand des Beispieles aus Abschnitt 2.3.4 erkl¨art.

In Abschnitt 2.3.4 wurde der Ausdruck a = do x:, y: -> yield y; end;mit Hilfe der ope-rationalen Semantik ausgewertet. F¨ur diesen Ausdruck ergibt sich folgender Baum:

LineNode

AssignNode

VariableNodea FunNode

AssignNode

LocalNodex

AssignNode

LocalNodey

LineNode

YieldNode

VariableNodey

Referenzzuweisung und Kopiezuweisung werden in der Implementierung durch den gleichen Knoten dargestellt. Der Ausdruck besteht aus der Kopiezuweisung einer Funktion an die Variable a, entsprechend ist die Wurzel des Baumes ein AssignNode mit VariableNode und FunNode als Kinder.

Die FunNode hat zwei AssignNode als Kinder, welche beim Ausf¨uhren der Funktion verwendet werden, um die Argumente zu ¨ubergeben.

Die LineNode enth¨alt den Rumpf der Funktion, welcher nur aus yield y also aus den Knoten

Der Ausdruck wird ausgef¨uhrt, indem die Methodeexecute()der Wurzel aufgerufen wird. Hier einmal der Quellcode dieser Methode:

R e f e r e n c e N o d e∗ AssignNode : : e x e c u t e ( ) { // e x e c u t e r i g h t s i d e

R e f e r e n c e N o d e∗ r i g h t R e f e r e n c e = r i g h t ->e x e c u t e ( ) ;

// i f r e t u r n was c a l l e d , s t o p any c h a n g e s t o t h e program ’ s s t a t e i f ( I n t e r p r e t e r : : i s R e t u r n i n g ) {

return r i g h t R e f e r e n c e ; }

// i f t h i s i s a r e f e r e n c e a s s i g n m e n t ( v a r := v a l u e ) i f ( r e f e r e n c e ) {

LeftNode∗ l e f t N o d e = dynamic cast<LeftNode∗>( l e f t ) ; i f ( l e f t N o d e == NULL) { throw . . . ; }

l e f t N o d e ->r e f e r e n c e V a l u e ( r i g h t R e f e r e n c e ->v a l u e ) ; return r i g h t R e f e r e n c e ;

}

// o t h e r w i s e t h i s i s a copy a s s i g n m e n t ( v a r = v a l u e ) R e f e r e n c e N o d e∗ l e f t R e f e r e n c e = l e f t ->e x e c u t e ( ) ;

l e f t R e f e r e n c e ->v a l u e ->become ( r i g h t R e f e r e n c e ->v a l u e ) ; delete r i g h t R e f e r e n c e ;

return l e f t R e f e r e n c e ; }

Und zum Vergleich eine der Regeln der operationalen Semantik:

KZuweisung (ν, ρ, e2) =⇒ (ν0, ρ0, r2),(ν0, ρ0, e1) =⇒ (ν00, ρ00, r1), ρ00(r2) =w (ν, ρ, e1=e2) =⇒ (ν00, ρ00[rw1], r1)

Wie an der Regel zu erkennen ist, muss zuerst die rechte Seite der Zuweisung ausgef¨uhrt wer-den. Dies geschieht in der Methode direkt in der ersten Zeile. Der Referenzwert r2 aus der Semantik, ist dann in der VariablerightReferencedes TypsReferenceNodeenthalten.

Danach wird die linke Seite ausgef¨uhrt und die Referenz in leftReference gespeichert. Die Anderung der¨ ρ00Funktion erfolgt durch die Zeile

leftReference->value->become(rightReference->value)

W¨urde es sich um eine Referenzzuweisung handeln, so m¨usste es nach der operationalen Seman-tik einige Fallunterscheidungen je nach Typ der Variable geben. Die entsprechenden Regeln RZusweisung, RZLokal, RZGlobal und RZObj geh¨oren zwar zu W¨ortern der Referenz-zuweisung, erfolgen aber trotzdem nicht in der KlasseAssignNode. Stattdessen werden die Regeln in den Klassen VariableNode, LocalNode und DotNode ausgef¨uhrt. Dies liegt daran, dass die Auswahl der richtigen Auswertungsregel nur von der Art der Variable abh¨angt. Es ist aus meiner Sicht also einfacher, diese Unterscheidung durch Polymorphie zu l¨osen, als durch viele einzelne F¨alle inAssignNode.

5.2.2 Abweichungen von FunNode zur operationalen Semantik

Die Funktionsdeklaration weicht in der Implementierung von der RegelFErzeugung der opera-tionalen Semantik ab. Die Regel lautet wie folgt:

FErzeugung ρ(r) nicht definiert, ζ(ν, e2) =e0 (ν, ρ,do [catch] e1 -> e2 end) =⇒ (ν, ρ[do [catch]re

1 -> e0 end], r)

Man beachte, dass durch ζ(ν, e2) eine Kopie von e2 erzeugt wird, in der alle ^var Ausdr¨ucke durch Referenzen ersetzt wurden. Gerade bei gr¨oßeren Funktionen ist die Erzeugung einer solchen Kopie sehr rechenintensiv und f¨uhrt auch zu einem h¨oheren Speicherverbrauch, wenn nicht einfach aufe2 referenziert werden kann.

In der Implementierung wird dies optimiert, indem jedes FunNode-Objekt ein Assoziatives Array mit allen im Funktionsrumpf auftauchenden^var Variablen als Schl¨usseln hat.

Wird eine Funktion deklariert, wird einfach nur eine flache Kopie des FunNode Objektes er-stellt, sodass der Funktionsrumpf nicht mitkopiert wird. Das Assoziative Array wird hingegen wohl mitkopiert und den^varSchl¨usseln werden die aktuellen Objekte, zu denen diese Variablen aufgel¨ost werden, zugeordnet.

Muss w¨ahrend einer sp¨ateren Ausf¨uhrung dieser Funktion eine^var Variable ausgewertet werden, so wird einfach das entsprechende Objekt im Assoziativen Array derFunNodeausgelesen.