Aufgabe 1 und 2 jay-…nderungen

Werbung
Aufgabe 1 und 2
jay-…nderungen
Ich habe zuerst Aufgabe 1 gelðst und diese Lðsung för Aufgabe 2 weiterentwickelt. Ich
zeige hier nur das Endergebnis.
Die Quellen der beiden Case-Klassen und deren Unterklassen wurden nach aufg2/sem und
aufg2/run kopiert. Weiterhin habe ich die jay-Datei um die break- und
continue-Anweisungen erweitert. Hinter den beiden neuen Anweisungen kann optional
(Aufgabe 2) ein Ausdruck folgen.
import aufg2.sem.Case;
import aufg2.sem.Jump;
%%
stmt :
|
|
|
|
...
BREAK
BREAK expr
CONTINUE
CONTINUE expr
{
{
{
{
$$ = new
parserAt
$$ = new
parserAt
Jump.Break(); }
= $1; $$ = new Jump.Break($2); }
Jump.Continue(); }
= $1; $$ = new Jump.Continue($2); }
%%
protected Hashtable symbolTable = new Hashtable();
{ ...
symbolTable.put("break",
new Integer(BREAK));
symbolTable.put("continue",new Integer(CONTINUE));
}
¶
Die semantische Analyse
Bei der semantischen Analyse der neuen Anweisungen muû geklÙrt werden, ob diese sich
in einer göltigen Umgebung befinden. Es darf z.B. kein break auûerhalb einer Schleife oder
auûerhalb einer case-Anweisung stehen. In der Klasse ck.sem.Stmt werden dazu zwei
Klassenvariablen nBreaks und nContinues eingeföhrt. Ein break ist z.B. erlaubt, wenn
nBreaks grðûer Null ist.
protected static int nBreaks = 0;
public int getNBreaks() { return nBreaks; }
protected static int nContinues = 0;
public int getNContinues() { return nContinues; }
Die semantische Analyse von Schleifen und die der Case-Anweisung zÙhlen die beiden
neuen Variablen nun hoch und runter:
Case:
public Type sem () {
if (! didSem) { didSem = true;
nBreaks++;
...
// alter Code
nBreaks--;
}
return type;
}
Stmt.While:
public Type sem () {
if (! didSem) {
nBreaks++; nContinues++;
...
// alter Code
nBreaks--; nContinues--;
}
return type;
}
¶
Die Klassen Break und Continue des Parse-Baums stammen als innere Klasse von
aufg2.sem.Jump auch von der Klasse Jump ab. Jump-Objekte habe einen Unterbaum. Der
Unterbaum ist bei einem break oder continue ohne den optionalen Ausdruck null, sonst der
Parse-Baum des Ausdrucks.
In der sem-Methode von Jump teilen sich Break- und Continue-Objekte Teile ihrer
semantischen Analyse. Generell sollen beide Anweisungen nur mit geraden Ebenenzahlen
arbeiten. Daher wird der optionale Unterbaum auf den richtigen Typ hin untersucht. Ist der
Typ nicht Int, wird Int nach einer mðglichen Umwandlung gefragt. Kann Int nicht wandeln,
wird der Typ des Unterbaums gefragt. Haben beide Anfragen keinen Erfolg, kommt es zu
einer Fehlermeldung. Bei Erfolg einer der Anfragen, wird der neue Parse-Baum in sub[0]
hinterlegt.
public class Jump extends Stmt {
public Jump () { this(null); }
public Jump (Sem expr) { sub = new Sem [] { expr}; }
public Type sem () {
if (! didSem) { didSem = true;
type = Int.self;
if(sub[0]!=null) {
Type st = sub[0].sem(); // check sub[0]
if(st == null)
// error in sub[0].sem occured
type = null;
else
// sub[0].sem was ok
if(st != Int.self) {// is type of sub[0] Int?
Sem t;
// ask Int to convert expr
t = Int.self.cast(Int.self, this, 0, st);
if(t == null)
// ask type of sub[0] to convert
t = st.cast(Int.self, this, 0, st);
if(t == null) { // cann't convert type
type = null;
error("cann't convert "+st+"to "+Int.self);
return type;
}
// convert was ok, store new tree
sub[0]=t;
}
}
}
return type;
// null indicates error
}
public static class Break extends Jump { ... }
public static class Continue extends Jump { ... }
}
¶
Die Klasse Break ruft in ihrer sem-Methode die der Oberklasse Jump auf. Danach ist der
optionale Unterbaum vom Typ Int, oder es ist ein Fehler aufgetreten. Als nÙchstes wird
gepröft, ob es in der aktuellen Umgebung öberhaupt break-Anweisungen geben darf. Dazu
wird die Klassenvariable nBreaks aus ck.sem.Stmt abgefragt. Wurde das break ohne den
optional folgenden Ausdruck benutzt, ist die Semantikanalyse hier zu Ende. Der Knoten im
Interpreterbaum wird gebaut und die Methode geht zu Ende. Hat das Objekt aber einen
Ausdruck als Unterbaum, wird dieser weiter untersucht. Ist der Unterbaum ein Value, kann
der Ausdruck mit den maximal erlaubten break- bzw. continue-Ebenen verglichen werden.
Weiterhin wird in diesem Fall ein anderer Konstruktor des Interpreter-Knotens verwendet.
Analog funktioniert die semantische Analyse der Klasse Continue und ist daher hier nicht
extra dargestellt.
public static class Break extends Jump {
public Break () { this(null); }
public Break (Sem expr) { super(expr); }
public Type sem () {
if (! didSem) {
super.sem();
// check expr and type of expr
if(type == null)
// a error occured, return
return type;
if(nBreaks<=0) {
// is the break in a correct environment?
type = null;
error("no break allowed");
return type;
}
if(sub[0] == null) { // no expr tree, build run tree and return
value = new aufg2.run.Jump.Break();
return type;
}
// is the expr a number ?
if(sub[0].getValue() instanceof Value) {
// get number of expr
int n = ((ck.run.Int)sub[0].getValue()).value;
if(nBreaks<= n) {
// check if the number of levels is ok
type = null;
// level isn't ok, return
error("no break "+n+"allowed");
return type;
}
// level is ok, build run tree with level
value = new aufg2.run.Jump.Break(n);
return type;
}
// build run tree with expr tree as level
value = new aufg2.run.Jump.Break(sub[0].getValue());
}
return type;
}
}
¶
Die Laufzeit-Klassen
Die Klassen JumpException und deren inneren Unterklassen BreakException und
ContinueException im Paket ck.run dienen zur Implemtierung der break- und
continue-Anweisung. Eine JumpException kapselt eine Integer-Zahl. Die Zahl reprÙsentiert
die break- oder continue-Ebenen. Werden Objekte mit einer Ebenenzahl unter eins erzeugt,
kommt es zu einem Fehler. šber die Methode getLevel kann die aktuelle, interne
Information einer JumpException erfragt werden. Dabei wird diese auch gleich um eines
erniedrigt. Sinkt die Ebene dabei unter eins, darf getLevel nicht weiter aufgerufen werden.
package ck.run;
public abstract class JumpException extends RuntimeException {
protected int n;
public JumpException () { this(1); }
public JumpException (int n) {
this.n=n;
if(n<1)
throw new RuntimeException("illegale JumpException with"+
" level "+n+" created");
}
public String toString() {
return getClass().getName()+" level "+n; }
public int getLevel () {
if(n==0)
throw new RuntimeException("illegale JumpException,"+
" level is 0");
return n--;
}
public static class BreakException extends JumpException {
public BreakException () { this(1); }
public BreakException (int n) { super(n); }
}
public static class ContinueException extends JumpException {
public ContinueException () { this(1); }
public ContinueException (int n) { super(n); }
}
}
¶
Die Klasse aufg2.run.Jump bzw. deren inneren Unterklassen Break und Continue kapseln
die Knoten im Interpreterbaum der beiden neuen Anweisungen. Jump-Objekte kennen
entweder ihre break- bzw. continue-Level als Zahl oder berechnen sich diese als Ergebnis
eines Ausdrucks als Unterbaum. Der Unterbaum ist durch die semantische Analyse immer
vom Typ Int.
In eval von Break oder Continue werden dann einfach die zugehðrigen JumpExceptions
erzeugt.
public abstract class Jump extends Run {
int n;
public Jump () { this(1); }
public Jump (int n) { this.n = n; }
public Jump (Run expr) { sub = new Run [] { expr }; }
public abstract Run eval () throws Exception;
public static class Break extends Jump {
public Break () { super(); }
public Break (int n) { super(n); }
public Break (Run expr) { super(expr); }
public Run eval () throws Exception {
if(sub == null)
throw new JumpException.BreakException(n);
throw new JumpException.BreakException(
((Int)sub[0].eval()).value);
}
}
public static class Continue extends Jump { ... }
// analog
}
Die von Break- und Continue-Objekten erzeugten JumpExceptions werden nun in eval der
Klassen ck.run.While und aufg2.run.Case abgefangen und bearbeitet.
In While werden beide Arten der Exceptions abgefangen. Ist die JumpException för eine
weiter auûen gelegene Kontrollstruktur, wird die Exception einfach erneut ausgelðst. Der
Level in der JumpException wurde bereits bei Aufruf der Methode getLevel runtergezÙhlt.
Ist die JumpException för das while selber, wird je nach Art der JumpException ein break
oder ein continue ausgeföhrt.
public class While extends Run {
...
public Run eval () throws Exception {
Run result = null;
while (((Bool)sub[0].eval()).value)
try {
if (sub[1] != null) result = sub[1].eval();
} catch (JumpException e) {
if(e.getLevel()>1) throw e;
if(e instanceof JumpException.BreakException)
break;
continue;
}
return result;
}
}
¶
Die Methode eval der Klasse aufg2.run.Case hat sich gegenöber dem letzten Aufgabenblatt
leicht geÙndert. In einer ersten for-Schleife wird der erste wahre case-Zweig oder der erste
default-Zweig gesucht. default-Zweige bestehen Dank der semantischen Analyse im
Gegensatz zu case-Zweigen nicht aus einem if. Ist der Einstiegs-Punkt gefunden, werden
nun alle weiteren case-Zweige ohne testen der Bedingung und alle weiteren default-Zweige
ausgeföhrt. Damit kommt es zu einem fall through bei den Zweigen. Beim Ausföhren
werden BreakException abgefangen und bearbeitet, man kann damit die Zweige durch
break-Anweisungen abbrechen. ContinueExceptions werden nicht abgefangen.
public class Case extends Run {
...
public Run eval () throws Exception {
int n;
Run result = null;
for (n = 0; n < sub.length; ++ n)
if ( sub[n] instanceof If) {
if(((Bool) ((Run)sub[n].sub(0)) .eval()) .getValue() )
break;
} else
// default
break;
for (; n < sub.length; ++ n)
// eval with fall through
try {
if (sub[n] instanceof If)
result = ((Run)sub[n].sub(1)) .eval();
else
// default
result = sub[n].eval();
} catch (JumpException.BreakException e) {
if(e.getLevel()>1)
throw e;
break;
}
return result;
}
}
¶
Bemerkung
- In dieser Lðsung föhren negative Ausdröcke oder ein Nullwert hinter break oder continue
zu einem Laufzeitfehler beim Erzeugen der JumpException bzw. zu einem Fehler in der
Semantik-Analyse. Eine andere Idee ist, daû bei einem negativen Wert die ZÙhlrichtung
einfach umgekehrt wird. Dann muû man auch zur Laufzeit Variablen der Art nBreaks und
nContinues mitföhren. Bsp.:
float f;
program
while 2=2 do
while 3=3 do
while 4=4 do
read f;
if f > 3 then
continue f-3
fi;
if f>=-3 then
break f
fi;
continue f+3
od;
od;
od;
Innerhalb der innersten while-Schleife wÙre ein break 1 analog zu break -3 und continue 3
analog zu continue -1.
- In dieser Lðsung wurde das CompilerKit selber geÙndert. Rein theoretisch geht das auch
ohne, erfordert dann aber mehr Arbeit. Man möûte Code aus dem Kit duplizieren, wie z.B.
bei den Klassen ck.sem.Stmt.While und ck.run.While.
- Die Kðrper einer Funktion bzw. einer Prozedur und das Hauptprogramm werden
unabhÙngig voneinander semantisch gepröft. Damit kann es auch keine Probleme beim
Hoch- und RunterzÙhlen von nBreaks und nContinues geben. Damit kann ein break bzw.
continue nie aus einer Funktion bzw. Prozedur herauszeigen. Vielleicht mðchte man
dieses bei spÙteren lokalen Funktionen und Prozeduren aber wieder erlauben. Diese
dörften dann nicht separat getestet werden. Bsp.:
while cond {
func x { break }
...
}
¶
Tests
Im Katalog q finden Sie einige Tests. In mixed/cmp/makefile kðnnen Sie beim Ziel test
diese durch ein- und auskommentieren testen.
Hier das Beispiel break_continue_expr.q:
float f;
program
while 1=1 do
write "at start of loop 1\n";
while 2=2 do
write "at start of loop 2\n";
while 3=3 do
write "at start of loop 3\n";
read f;
if f > 3 then
continue f-3
fi;
if f>=-3 then
break f
fi;
continue f+3
od;
write "after loop 3\n"
od;
write "after loop 2\n"
od;
write "after loop 1\n"
Eine Ausföhrung:
suleika $ 5 CLASSPATH=../..:$HOME/cb/Uebungen java mixed.cmp.Cmp2 \
> ../../q/break_continue_expr.q
at start of loop 1
at start of loop 2
at start of loop 3
1
after loop 3
at start of loop 2
at start of loop 3
2
after loop 2
at start of loop 1
at start of loop 2
at start of loop 3
3
after loop 1
¶
suleika $ 5 CLASSPATH=../..:$HOME/cb/Uebungen java mixed.cmp.Cmp2 \
> ../../q/break_continue_expr.q
at start of loop 1
at start of loop 2
at start of loop 3
4.1
at start of loop 3
5.2
at start of loop 2
at start of loop 3
6
at start of loop 1
at start of loop 2
at start of loop 3
Zahlen grðûer gleich sieben und Zahlen kleiner eins föhren verstÙndlicherweise zu Fehlern.
¶
Aufgabe 3
In Aufgabe 3 sollte ein cast-Operator analog zu C eingebaut werden. Ich habe mich auch in
Bezug auf Vorrang und Bewertungsreihenfolge an C gehalten, d.h. der cast-Operator hat
den gleichen Vorrang wie die unÙren Operatoren ~, + und - und wird von rechts her
bewertet. Auch die Syntax ist analog zu C. In dem Parse-Baum wird ein Objekt der Klasse
Cast för einen cast-Operator eingebaut. Die Objekte bekommen als ersten Paramter den
Typ, zu dem hin gewandelt werden soll, und als zweites Argument den zu wandelnden
Ausdruck. För das Eingabesymbol ) werden Strings auf den Werte-Stack gelegt. Damit
kann man die Zeilennummer durch Zuweisen an parseAt zuröcksetzen.
import aufg3.sem.Cast;
%token
%type
<String>
<Type>
',', '!', '(', ')'
cast_types
%right
<String>
'~'
%%
expr : ...
| '~' expr
{ parserAt = $1;
$$ = new Unary.Complement($2); }
| '(' cast_types ')' expr %prec '~' { parserAt = $3;
$$ = new Cast($2, $4); }
| '+' expr %prec '~'
{ $$ = $2; }
| '-' expr %prec '~'
{ parserAt = $1;
$$ = new Unary.Minus($2); }
| ...
cast_types
| FLOAT
: INT
{ $$ = Int.self; }
{ $$ = Flt.self; }
%%
¶
Die Klasse Cast stammt aus dem Paket aufg3.sem und stammt von ck.sem.Unary ab, da
sie einen Unterbaum besitzt. In dem Konstruktor wird als Typ des Objekts der im ersten
Argument angegebene Typ hinterlegt und der als zweites Argument angegebene
Unterbaum öber einen super-Aufruf im sub-Array abgelegt. In der sem-Methode wird als
erstes der Unterbaum gepröft. Hattet diese Erfolg, werden die Typen des Unterbaums und
des Cast-Objekts verglichen. Sind die Typen gleich, muû nichts weiter getan werden. Der
Interpreterbaum des Unterbaums kann einfach öbernommen werden. Sind die Typen
verschieden, muû ein Umwandlungsknoten eingebaut werden. Zuerst wird der Typ des
Cast-Objekts gefragt, ob es die Umwandlung kann. Wenn nicht, so wird der Typ des
Unterbaums gefragt. Schlagen beide Anfragen fehl, kann der Unterbaum nicht gecastet
werden, und es wird eine Fehlermeldung ausgegeben. Hatte eine der Anfragen Erfolg, so
wird der bei der Anfrage gelieferte Umwandlungsknoten in sub[0] hinterlegt und value
öbernommen.
Es gibt natörlich kein aufg3.run-Paket.
package aufg3.sem;
import ck.sem.Type;
import ck.sem.Unary;
import ck.sem.Sem;
public class Cast extends Unary {
public Cast(Type t, Sem node) {
super(node);
this.type = t;
}
public Type sem () {
if (! didSem) {
didSem=true;
if(sub[0].sem() == null )
type = null;
else if (type != sub[0].sem()) {
// convert ?
Sem t;
Type st = sub[0].sem();
if((t = type.cast(type, sub[0], st)) == null)
if((t = st.cast(type, sub[0], st)) == null) {
type = null;
error(this+": can't cast"+st+" to "+type);
return type;
}
sub[0] = t;
value = t.getValue();
} else
value = sub[0].getValue();
}
return type;
}
}
¶
Das waren schon alle …nderungen. Wir wollen uns wieder an einem kleinen Beispiel
(case.q) die BÙume ansehen.
float f =
program
write
write
write 3 /
(float) (int) 4.3;
3 / 4, "\n";
3 / (float) 4, "\n";
(int) f, "\n"
Hier der Baum vor der semantischen Analyse. Die Cast-Knoten sind fett hervorgehoben.
Seq
Binary$Assign
f: Flt$Variable Flt$Self
aufg3.sem.Cast
aufg3.sem.Cast
Flt Flt$Self: Flt 4.3
Unary$Write
Binary$Div
Int Int$Self: Int 3
Int Int$Self: Int 4
Unary$Write
Str Str$Self: Str "\n"
Unary$Write
Binary$Div
Int Int$Self: Int 3
aufg3.sem.Cast
Int Int$Self: Int 4
Unary$Write
Str Str$Self: Str "\n"
Unary$Write
Binary$Div
Int Int$Self: Int 3
aufg3.sem.Cast
Unary$Ref
f: Flt$Variable Flt$Self
Unary$Write
Str Str$Self: Str "\n"
¶
Hier der Baum nach der semantischen Analyse. Cast- und Umwandlungs-Knoten sind
hervorgehoben.
Seq Str$Self
Binary$Assign Flt$Self
f: Flt$Variable Flt$Self
aufg3.sem.Cast Flt$Self: Flt 4.0
Flt$FromInt Flt$Self: Flt 4.0
aufg3.sem.Cast Int$Self: Int 4
Flt$ToInt Int$Self: Int 4
Flt Flt$Self: Flt 4.3
Unary$Write Int$Self
Binary$Div Int$Self: Int 0
Int Int$Self: Int 3
Int Int$Self: Int 4
Unary$Write Str$Self
Str Str$Self: Str "\n"
Unary$Write Flt$Self
Binary$Div Flt$Self: Flt 0.75
Flt$FromInt Flt$Self: Flt 3.0
Int Int$Self: Int 3
aufg3.sem.Cast Flt$Self: Flt 4.0
Flt$FromInt Flt$Self: Flt 4.0
Int Int$Self: Int 4
Unary$Write Str$Self
Str Str$Self: Str "\n"
Unary$Write Int$Self
Binary$Div Int$Self
Int Int$Self: Int 3
aufg3.sem.Cast Int$Self
Flt$ToInt Int$Self
Unary$Ref Flt$Self
f: Flt$Variable Flt$Self
Unary$Write Str$Self
Str Str$Self: Str "\n"
¶
Hier der Interpreterbaum. Umwandlungsknoten sind hervorgehoben. Cast-Knoten kann es
nicht mehr geben.
Seq
Variable$Assign: Flt$Variable @135058536
Flt 4.0
Int$Write
Int 0
Str$Write
Str "\n"
Flt$Write
Flt 0.75
Str$Write
Str "\n"
Int$Write
Int$Div
Int 3
Flt$ToInt
Variable$Ref: Flt$Variable @135058536
Str$Write
Str "\n"
Die Ausgabe:
suleika cmp $ make test
CLASSPATH=../..:$HOME/cb/Uebungen java mixed.cmp.Cmp3 ../../q/cast.q
0
0.75
0
Herunterladen