Eksamen i : UNIVERSITETET I OSLO Det matematisk-naturvitenskapelige fakultet med svar INF5110 - Kompilatorteknikk Eksamensdag : Onsdag 3. juni 2014 Tid for eksamen : 14.30-18.30 Oppgavesettet er på : Vedlegg : Tillatte hjelpemidler : 7 sider (pluss 1 side vedlegg) 1 side (side 8). Hvis denne siden brukes til besvarelse av spørsmål 3, da rives den ut og leveres i hvit besvarelse lle trykte og skrevne Les gjennom hele oppgavesettet før du begynner å løse oppgavene. Dersom du savner opplysninger i oppgavene, kan du selv legge dine egne forutsetninger til grunn og gjøre rimelige antagelser, så lenge de ikke bryter med oppgavens "ånd". Gjør i så tilfelle rede for disse forutsetningene og antagelsene. Oppgave 1 (35%) Vi ser på følgende grammatikk G1: # # = a Her er eneste ikke-terminal, og er dermed også start-symbol. I tillegg til #, = og a er også $ terminalsymbol, med vanlig betydning. 1a Finn First og Follow til i G1 Svar 1a: First() = { #, a. Follow() = { =, $. 1b Tegn LR(0)-DFen til G1 (med vanlig tillegg av én produksjon). Nummerer tilstandene fra 0 og oppover.
Svar 1b G1: # # = a 0 1... #. # = 2. a a a. # a 3 4 #. #. =. #. # =. a 5 # # #. #. = = # =. a. #. # = 6. a # =. 1c Gå gjennom alle tilstandene i LR(0)-DFen til G1 og angi for hver av dem om de har noen SLR(1)- konflikter, og i så fall hvilke. Forklar. Svar 1c Tilstander: 0 Skift for a og #. Er SLR(1)-tilstand 1 Bare red. med (= «accept») for $ ( Follow = {$). Er SLR(1). 2 Bare red. med a for = og $. Er SLR(1). 3 Skift for a og #. Er SLR(1). 4 Skift for = og red. med # for = og $. ltså konflikt for =. Er ikke SLR(1) 5 Skift for a og #. Er SLR(1). 6 Reduksjon med # = for = og $. Er SLR(1). (Ikke spurt om, men: På grunn av tilstand 4 er grammatikken altså ikke SLR(1) ) 1d Vis at G1 er flertydig. Svar 1d Denne grammatikken er flertydig på samme måte som «dangeling else» i if-setninger, om vi ser # som «if then» og = som «else». Vi kan da tenke på at to «if»er og én «else» der gav problemer («hvilken if har else»), og dermed kan vi forsøke med følgende setning: # # a = a. Denne kan avledes fra på to måter:
# # a = a # # a = a Dermed kunne vi også vite at under 1c kunne grammatikken ikke bli SLR(1), siden alle SLR(1)- grammatikker (og andre LR-grammatikker) er entydige. 1e Du skal her sette opp en SLR(1)-parseringstabell for G1. Om grammatikken ikke er SLR(1) (og du derved får flere mulige aksjoner i en eller flere «tabell-ruter»), skal du gjøre et fornuftig valg av aksjon blant de aktuelle i denne «ruten». ngi i hvilke tilstander du da må gjøre slike eksplisitte valg, og begrunn hvorfor du gjør de valgene du gjør. Svar 1e # a = $ ------------------------------------------------------------------------------------------------------------ 0 s3 s2 1 1 accept 2 r( a ) r( a ) 3 s3 s2 4 4 NB: r( # ) / s5 r( # ) 5 s3 s2 6 6 r( # = ) r( # = ) ------------------------------------------------------------------------------------------------------------- Her er det altså to muligheter i rute [4, =], nemlig reduksjon med r( a ) og skift. En generell regel sier her at det ved skift/reduser-konflikter lønner seg å heller gjøre skift enn reduksjon. Dette fører her til at vi binder = til nærmeste #, slik vi også gjorde for if-then-else-setninger. (Det var ikke spurt om det, men det motsatte valget ville føre til en parser som ikke virker). 1f Vi skal se på LL(1)-analyse (f.eks. med «recursive descent») av setninger i G1. Gjør «tradisjonelle» forandringer (angitt i boka) på G1 slik at den nye grammatikken, G2, kan egne seg bedre for slik analyse. ngi G2. Svar 1f G1 er altså: # # = a Boka sier at to ting hvertfall må vekk om vi skal ha håp om å kunne gjøre LL(1)-analyse, nemlig venstre-rekursjon og at to alternativer starter på samme måte. Venstre-rekursjon finnes ikke, men de to første alternativene begynner på samme måte. Dette kan ordnes med «venstre-faktorisering», og man får da følgende grammatikk G2 (der L er ment å stå for «likhets-del» tilsvarende «elsedel»):
# L a L = ε (Ikke spurt om: Nå kunne man jo gjette ut fra at G1 var flertydig (og dermed ikke LL(1)), at heller ikke G2 er LL(1). Men G2 kunne danne grunnlaget for en recursive descent parser, om man bare fortsatte å lese i situasjoner tilsvarende den i rute [4, =] i oppgave 1e.) 1g vgjør om L(G1) er et regulært språk. Forklar! Svar 1g Språket L(G1) er ikke regulært. Dette kan man se av at en setning av formen: # #... # a = a = a = a med vilkårlig mange # -tegn og «= a»-grupper, er med i L(G1) hvis og bare hvis det er minst like mange # -tegn som «= a»-grupper. Men dette kan ikke være et regulært språk fordi en automat med et endelig antall tilstander ikke kan «telle opp» og ta med seg videre antall # -tegn, slik at dette kan sjekkes mot antall «= a»-grupper i resten av setningen. Oppgave 2 (20%) Vi antar at vi har et objekt-orientert språk, hvor alle metoder i klasser er virtuelle slik at de kan redefineres i subklasser. En standard måte å implementere tilstandsmaskiner i et slikt språk er å representere tilstander ved objekter av tilstandsklasser som er blad -klasser i et klassehierarki. Mulige hendelser i de forskjellige tilstander representeres som virtuelle metoder, og disse redefineres eventuelt i de forskjellige tilstands-subklasser. Koden under definerer en klasse Watch som en tilstandsmaskin med tilstandene Display, Hours og Minutes. Klokken endrer tilstand ved at det trykkes på en mode-knapp, som (indirekte, se under) fører til et kall på metoden mode. I tilstanden Display vises bare tiden, mens man i de andre tilstander kan øke enten timer eller minutter med 1 ved å trykke på incr-knappen som fører til et kall på metoden incr. Tilstandsklassene er definert som indre klasser i Watch for at variablene time og currentstate skal være tilgjengelige i metodene i de tilstandsklassene. Klassen Time definerer hvordan tid representeres, og vi forutsetter at den har metodene incrhours og incrminutes. class Watch { Time time = new Time(); State currentstate; void mode(){ currentstate.mode(); void incr(){ currentstate.incr(); Watch(){ currentstate = new Display(); class State { void mode(){... void incr(){... class Hours extends State { void mode(){currentstate = new Minutes(); void incr(){ /* 1 */ time.incrhours(1); class Minutes extends State { void mode(){currentstate = new Display(); void incr(){ time.incrminutes(1); class Display extends State { void mode(){currentstate = new Hours();
void settime(time newtime){time = newtime; class Time {... Vi antar at det i tillegg finnes et objekt av en klasse WatchInterface, som i en metode sense registrerer trykk på knapper som svarer til metodene mode og incr, og som kaller mode eller incr på det Watch-objektet som er tilknyttet WatchInterface-objektet. Her vises det utsnitt av sense hvor incr kalles: class WatchInterface { Watch thewatch = new Watch(); void sense(){...; if <mode button pressed> then thewatch.mode() else if <incr button pressed> then thewatch.incr();... 2a Tegn runtime-stakken som den ser ut som et resultat av dette kallet, rett før incr skal utføre time.incrhours(1), altså at utførelsen er ved /* 1 */ i koden ovenfor. Starten på stakken er en activation record som svarer til et kall på sense. nta at thewatch-objektet er i tilstanden Hours. Ta bare med activation records og ikke objekter, og for activation records tar du bare med control links. Svar 2a 2b Lag virtuell-tabellene for objekter av klassene State, Hours, Minutes og Display. For hvert element i tabellene skal du bruke notasjonen <klasse>::<metode> for å angi hvilken metode som gjelder. Ta høyde for at det finnes subklasser av Display som ikke er tatt med i koden over.
Svar 2b State 0 State::mode 1 State::incr Hours 0 Hours::mode 1 Hours::incr Minutes 0 Minutes::mode 1 Minutes::incr Display 0 Display::mode 1 State::incr 2 Display::setTime 2c Metoden settime kalles fra en metode i en klasse i Watch som ikke er tatt med i koden ovenfor. settime kalles som et resultat av at klokken får et radiosignal med ny (korrekt) tid, og den inneholder følgende kode:... // receive new time via radiolink (Display)currentState.setTime(receivedTime);... Man vil bare kalle settime mens klokken er i tilstanden Display, derfor forsøker man seg med denne type-castingen, som vil gi en runtime feil hvis currentstate ikke er et objekt av klassen Display. Dette er ikke den optimale måten å gjøre det på, men slik ble det gjort. Ovenfor er det eksempel på en down-cast (fra State til Display), men vi antar at man kan gjøre både down-cast og up-cast. Ovenfor har vi sett at objekter har en peker til en virtuell-tabell. Hva trenger du å vite om objekter, i tillegg til virtuell-tabellen, for å implementere en slik casting. Beskriv dette og lag en skisse av den koden som skal utføres for å gjøre denne testen. Svar 2c Man må vite klassen til objektet, og klassen må representeres på en slik måte at du har informasjon om dens superklasse, for eksempel: class Class { String Name; Class super; 2d Hvordan vil kompilatoren bruke denne castingen i sjekking av statisk semantikk? Svar 2c Typen til currentstate er State, og settime er i symboltabellen (eller i syntakstreet) ikke definert i State. Castingen gjør at man skal slå opp settime i symboltabellen tilsvarende klassen Display. 2e Ett kall på awatch.mode() fører til et kall på currentstate.mode(). Hva oversettes dette kallet til?
Svar 2c Kall på currentstate.vt[index(mode)] 2f Man ser at klassen State egentlig bare har de samme metoder som den omsluttende klasse (her Watch) og at man bare bruker virtuell-tabellene for State-objektene. Man går derfor over til et annet objekt-orientert språk, der følgende tre ting gjelder: 1. virtuelle metoder kan redefineres i indre klasser som er definert som STTE-klasser (se syntaks for STTE-klasser i koden under) 2. virtuell-tabellen pekes ut av en predefinert variabel _STTE 3. ved å assigne en STTE-klasse til _STTE endrer man virtuell-tabellen til den som gjelder for STTE-klassen. class Watch { Time time; void mode(...) void incr(...) Watch (){ _STTE = Display; class Hours STTE { void mode(){ _STTE = Minutes; void incr(){ time.incrhours(1); class Minutes STTE { void mode(){ _STTE = Display; void incr(){ time.incrminutes(1); class Display STTE { void mode(){ _STTE = Hours; void settime(time newtime){time= newtime; Hva oversettes awatch.mode() da til? Svar 2c awatch._stte[index(mode)] Oppgave 3 (20%) I denne oppgaven bruker vi språket som ble innført i 2f. Det følgende er et fragment av grammatikken for språket. Vi har bare tatt med de detaljer i språket som er aktuelle for oppgaven: classdecls classdecls ; classdecl classdecls classdecl classdecl class name { methoddecls classdecls classdecl class name STTE{ methoddecls methoddecls methoddecls ; methoddecl methoddecls methoddecl methoddecl type name () body
type int type bool type void Ord i kursiv er ikke-terminaler, ord og tegn i fet skrift er terminal-symboler, mens name representerer et navn som scanneren leverer. Det kan antas at name har attributtet name. I dette språket er en metode uten parametere og med typen void en såkalt event-metode. Den semantiske regelen er at en event-metode i en indre STTE-klasse må matche en event-metode i den omsluttende klasse. Den semantiske regelen som uttrykker akkurat dette er allerede lagt inn i attributtgrammatikken (ved attributtet error i methoddecl. MERK: Her var det en trykkfeil i oppgaven, det stod «match» i stedet for «error»), og denne reglen baserer seg på følgende attributter: classdecl eventmethods: set of names of event-methods for this class enclosingeventmethods: set of names of event-methods from enclosing class that should match the event methods for this class in case it is a STTE class methoddecls enclosingeventmethods: set of names of event-methods from enclosing class that should match the event methods among methods represented by this methoddecls shallmatcheventmethod: true if the methods represented by this methoddecls are methods of a STTE class, false otherwise methoddecl enclosingeventmethods: set of names of event-methods from enclosing class that shall contain the name of the method represented by this methoddecl shallmatcheventmethod: true if method of a STTE class, false otherwise error: true if method of a STTE class matches an event method of the enclosing class, false otherwise Trykkfeil, denne manglet: eventmethods: The (set of) the name of the method declaration. Det brukes følgende funksjoner, samt union av mengder (+): set(name): Lager en mengde med name som element s.lookup(name): true if name is in the set, otherwise false Gjør ferdig de semantiske regler for disse attributter. Du kan besvare dette spørsmålet ved å bruke vedlegget side 8 eller ved å lage en tilsvarende tabell selv.
Svar 3 (se også egen figur lagt ved som eget dokument) Grammar Rule classdecls 1 classdecls 2 ; classdecl classdecls classdecl Semantic Rule classdecls 2.enclosingEventMethods = classdecls 1.enclosingEventMethods classdecl.enclosingeventmethods = classdecls 1.enclosingEventMethods classdecl.enclosingeventmethods = classdecls.enclosingeventmethods classdecl class name { methoddecls classdecls classdecl.eventmethods = methoddecls.eventmethods classdecls.enclosingeventmethods = classdecl.eventmethods methoddecls.shallmatcheventmethod = false classdecl class name STTE { methoddecls methoddecls 1 methoddecls 2 ; methoddecl methoddecls.shallmatcheventmethod = true methoddecls.enclosingeventmethods = classdecl.enclosingeventmethods methoddecls 2.enclosingEventMethods = methoddecls 1.enclosingEventMethods methoddecl.enclosingeventmethods = methoddecls 1.enclosingEventMethods methoddecls 1.eventMethods = methoddecls 2.eventMethods + methoddecl.eventmethods methoddecls 2.shallMatchEventMethod = methoddecls 1.shallMatchEventMethod methoddecl.shallmatcheventmethod = methoddecls 1.shallMatchEventMethod methoddecls methoddecl methoddecls.eventmethods = methoddecl.eventmethods methoddecl.enclosingeventmethods = methoddecls.enclosingeventmethods methoddecl.shallmatcheventmethod = methoddecls.shallmatcheventmethod methoddecl type name () body if type.type= void then methoddecl.eventmethods = setof(name) (Trykkfeil, må ha: else methoddecl.eventmethods = Ø ) error = if type.type = void and methoddecl.shallmatcheventmethod then if methoddecl.enclosingeventmethods.lookup(name) then true else false
Oppgave 4 (25%) Vi skal her se på kodegenerering for «lange» if-then-else setninger av formen: if <condition> then <statement> elseif <condition> then <statement> elseif <condition> then <statement> elseif <condition> then <statement> else <statement> endif Her er elseif et nytt nøkkelord, og meningen med setningen skulle fremgå av syntaksen. Det kan være null eller flere elseif-grener, og null eller én else-gren til slutt. 4a Skissér et flyt-diagram (med bokser som angir koden til setninger og betingelser) som viser hvordan denne setningen kan oversettes, og angi spesielt hvordan «labler» og hopp skal plasseres. Svar 4a Betingelse 1 Test på true/false Setning 1 Betingelse 2 Test på true/false Setning 2...... Betingelse n Test på true/false Setning n False True False True False True Ubet. hopp Ubet. hopp Kommentar: Det er her ikke helt klart ut fra oppgaven akkurat hvordan denne tegningen skal se ut. Å bruke piler som her er OK, men om det i stedet er brukt noen betingede og ubetingede hopp-instruksjoner, og noen velplasserte labler som det hoppes til, så er det også OK. Tegninger lik den til venstre, men uten de tre true-pilene er også OK (siden programmet da ville falle gjennom, til setningen under, som er det riktige). Tegningen er laget ut fra et det er en elsegren til slutt (Setning n+1). Om det ikke er det blir det noen opplagte forandringer. Vi krevet ikke at man skulle omtale det. Setning n+1 Ubet. hopp
4b Vi antar at setninger beskrevet i forrige punkt er representert i det abstrakte syntakstreet med følgende nodeklasser (i Java): class Statement extends { // Vi bryr oss ikke med klassehierarkiet over dette nivået abstract void codegen( ); static String getnewlabel( ) { er ferdig programmert // genererer nye label-navn class IfStatement extends Statement { ElseIf first; /* Peker til første i en liste av ElseIf-objekter (se klassen ElseIf under). Merk at også «condition/statement»-paret som følger direkte etter første if er med i denne listen, slik at den alltid inneholder minst ett element. */ Statement elsestat; // NULL om det ikke er else-gren void codegen { // Denne skal du skrive! Og vi anbefaler at all kodegenerering for if-setningen gjøres her. class ElseIf extends { Condition cond; Statement stat; ElseIf next; // NULL om den er den siste i elseif-lista // nbefaling: Ingen codegen( ) her. ll T-kode generes da fra codegen() i IfStatement. class Condition extends { String codegen() { /* Denne skal du kalle, men ikke skrive. Den vil generere kode som beregner verdien av betingelsen (true eller false), og vil leverere en String som er navnet på den variabelen der svaret vil ligge. Se detaljer under. */ Vi antar at treet for det aktuelle programmet er satt opp før vi starter kodegenereringen. Merk altså at det her bare er den codegen-metoden som behandler «Condition» som leverer en String. Denne String en vil altså være navnet på den (kanskje temporære) variablen der svaret (true eller false) vil ligge etter at denne «Condition» er beregnet under utførelsen. For å få lagt ut en T-instruksjon kan du kalle metoden «void emit(string instr){» (som er direkte synlig). Denne er ferdig skrevet, og instruksjonene fra påfølgende kall vil da bli lagt pent etter hverandre på en fil. De T-instruksjonene du trenger burde være kurante (og det er ikke nøye om de har akkurat samme syntaks som i boka).
Oppgaven: Les kommentarene i klassene over nøye, og skriv så innmaten av void codegen( )» i IfStatement (slik som antydet over). Vi anbefaler altså at du skriver denne metoden slik at den gjør all kodegenerering for hele if-setningen, men det er også OK om du legger noe av arbeidet inn i en «codegen»-metode i ElseIf-klassen. Svar 4.b Først figuren fra 4.a: Betingelse 1 Test på true/false Setning 1 Betingelse 2 Test på true/false Setning 2...... Betingelse n Test på true/false Setning n False True False True False True Ubet. hopp Ubet. hopp Setning n+1 Ubet. hopp Labfter void codegen( ) { String labnext ; // Lablen på kommende betingelse String labfter = getnewlabel( ); // Lablen bak hele vår setning String tempname; ElseIF current = first; while (current!= null) { labnext = getnewlabel( ); tempname = current.cond.codegen( ); emit( if_false + tempname + goto + labnext ); current.stat.codegen ( ); emit ( goto + labfter); emit ( label + labnext); // Dette gir også en riktig label foran en eventuell else-setning current = current.next; If (elsestat!= null) { elsestat.codegen( ); emit ( label + labfter);
4c Du skal her skrive codegen( )-metoden i klassen IfStatement (se 4b) en gang til, men under litt andre betingelser. Denne gangen antar vi at codegen-metoden i klassen Condition har to stringparametere (som er label-navn), og at metoden ikke leverer noen String. Når den kalles skal den (som parametre) få med «labler» som angir stedene man skal hoppe til dersom betingelsen blir hhv. true og false. Koden den genererer vil alltid hoppe til en av disse lablene (og altså aldri «falle gjennom» til instruksjonen etter). Klassen Condition er slik: class Condition extends { void codegen(string truelab, falselab) { // Denne skal du kalle, men ikke skrive. Les teksten over om hva den gjør. Oppgaven: Skriv en ny utgave av codegen()-metoden i klassen IfStatement under de betingelser som er beskrevet over. Det er viktig her at alle hopp blir så direkte som mulig. Svar 4c Svaret her blir nokså likt det på 4b. Under er det som er nytt i forhold til 4b angitt i blått, og det som skal vekk er skrevet i grønt med strek over: void codegen( ) { String labstat; // Label på førstkommende setning String labnext; // Label på førstkommende betingelse String labfter = getnewlabel( ); String tempname; ElseIF current = first; while (current!= null) { labstat = getnewlabel( ); labnext = getnewlabel( ); tempname = current.cond.codegen(labstat, labnext ); emit( if_false + tempname + goto + labnext ); emit ( label + labstat); current.stat.codegen ( ); emit ( goto + labfter); emit ( label + labnext); // Dette gir også en riktig label foran en eventuell else-setning current = current.next; If (elsestat!= null) { elsestat.codegen( ); emit ( label + labfter);
Vi tar også med en renskrevet versjon: void codegen( ) { String labstat; // Label på førstkommende setning String labnext; // Label på førstkommende betingelse String labfter = getnewlabel( ); ElseIF current = first; while (current!= null) { labstat = getnewlabel( ); labnext = getnewlabel( ); current.cond.codegen(labstat, labnext ); emit ( label + labstat); current.stat.codegen ( ); emit ( goto + labfter); emit ( label + labnext); // Dette gir også en riktig label foran en eventuell else-setning current = current.next; If (elsestat!= null) { elsestat.codegen( ); emit ( label + labfter);