Kompilatorer och interpretatorer: Lösningar till tentamen 2024-01-11

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta. Det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften, eller att de bara hänvisar till var man kan läsa svaren. Dessutom har det inträffat i världshistorien att lärare skrivit fel i lösningsförslagen. Jag har förstås aldrig gjort det, men andra. Är det verkligen någon som läser såna här inledande texter? Jag vet inte. Det kan vara så. Rabarber rabarber rabarber. Det har jag hört att statisterna får säga på filminspelningar när det ska vara bakgrundssorl från en restaurang eller liknande. Här nedan kommer lösningsförslagen till uppgifterna.

Uppgift 1 (5 p)

Betrakta följande C-program:

#include <stdlib.h>
#include <stdio.h>

int a = 1, b = 2;

void f(int c) {
    int* p;
    p = malloc(sizeof(int));
    *p = c;
    if (c < 3)
        f(c + 1);
    else
        printf("Här!\n");
}

void g(int d) {
    int a, e;
    int *p;
    a = 4;
    b = 5;
    e = 6;
    p = malloc(sizeof(int));
    *p = d;
    f(1);
}

int main(void) {
    int a, f;
    a = 7;
    b = 8;
    f = 9;
    g(10);
}

När programexekveringen kommer fram till raden med utskriften "Här!", antar vi att vi stoppar programmet och tittar på vilka olika variabler som finns i minnet.

I minnet kommer det då att finnas ett antal heltalsvariabler och andra lagringsutrymmen för heltal, som innehåller olika tal. Rita upp en skiss över dessa variabler och lagringsutrymmen, med innehåll, och förklara vad som är vad. Din förklaring bör bland annat innehålla termerna "statiska data", "stack", "heap", "aktiveringspost" och "parametrar".

Svar:

De olika delarna av minnet, som statiska data och heap, kan komma i en annan ordning, och de som växer kan växa åt ett annat håll än på bilden. En del av parametrarna och de lokala variablerna kan placeras i register av kompilatorn, men måste normalt ändå ha platser i minnet, så de kan sparas där vid anrop till en annan funktion (så den funktionen kan använda registren). c är en parameter till funktionen f, och d är en parameter till funktionen g.

Skiss över minnet

Uppgift 2 (5 p)

Vi skriver in C-programmet från uppgiften ovan, men vi råkar göra en del skrivfel. När vi försöker bygga och köra programmet får vi de fel- och varningsmeddelanden som visas nedan. I vilka av kompilatorns faser, eller vid andra tidpunkter, upptäcks vart och ett av de olika felen och varningarna?

(Man kan behöva åtgärda en del av felen för att komma vidare och få de andra felen.)

Programmet Meddelanden
#include <stdlib.h>
#include <stdio.h>

int a = 1, b = 2;

int f(int c) {
    int* p;
    *p = c;
    if (c < 3)
        f(c + 1);
    else
        printf("Här!\n");
}

void g(int d) {
    int a, e;
    int *p;
    a = 4;
    b = 5;
    e = 6;
    p = malloc(sizeof(int);
    *p = d;
    f();
}

int main(void) {
    int a, f;
    a = 7;
    b = 8;
    f = 9;
    h(10);
}







(1) Segmentation fault (core dumped)




(2) control reaches end of non-void function







(3) expected ")" before ";" token

(4) too few arguments to function "f"







(5) undefined reference to "h"

Svar:

(1) Vid programkörningen ("exekvering", "run-time")
(2) Semantisk analys
(3) Syntaktisk analys ("parser")
(4) Semantisk analys
(5) Länkning

Uppgift 3 (9 p)

Det här är ett programavsnitt i ett C-liknande språk:
a = b;
c = d;
while (e < f + g + h * i + j) {
    k = m + n;
}
if (o < p) {
    q = r;
}
else {
    s = t;
    u = v;
}
w = x;

Översätt ovanstående programavsnitt till två av följande tre typer av mellankod. (Skulle du svara på alla tre, räknas den med högst poäng bort.)

a) ett abstrakt syntaxträd (genom att rita upp trädet!)

Svar:

Man kan avsluta ";-listorna" med NULL i stället för den sista satsen i listan. Det blir lite mer att rita, men kan vara lite enklare programmeringsmässigt.

Ett (abstrakt) syntaxträd

b) postfixkod för en stackmaskin

Svar:

        lvalue a
        rvalue b
        =
        lvalue c
        rvalue d
        =
label WHILE-START:
        rvalue e
        rvalue f
        rvalue g
        +
        rvalue h
        rvalue i
        *
        +
        rvalue j
        +
        <
        gofalse AFTER-WHILE
        lvalue k
        rvalue m
        rvalue n
        +
        =
        goto WHILE-START
label AFTER-WHILE:
        rvalue o
        rvalue p
        <
        gofalse ELSE-START
        lvalue q
        rvalue r
        =
        goto AFTER-ELSE
label ELSE-START:
        lvalue s
        rvalue t
        =
        lvalue u
        rvalue v
        =
label AFTER-ELSE:
        lvalue w
        rvalue x

c) treadresskod

Svar:

        a = b
        c = d
label WHILE-START:
        temp1 = f + g
        temp2 = h * i
        temp3 = temp1 + temp2
        temp4 = temp3 + j
        if e ≥ temp4 goto AFTER-WHILE
        k = m + n
        goto WHILE-START
label AFTER-WHILE:
        if (o ≥ p) goto ELSE-START
        q = r
        goto AFTER-ELSE
label ELSE-START:
        s = t
        u = v
label AFTER-ELSE:
        w = x

Scenario till övriga uppgifter

Det är inte ovanligt med gator och hus, som Granvägen och Brickebergsvägen på den här bilden:

Satellitbild med gator och hus

Vi ska bygga en databas för att lagra data om gator och hus, och därför behöver vi ett särskilt inmatningsspråk. Med kommandona gata, hus och korsning ska man kunna mata in en beskrivning av vilka gator och hus som finns, och hur gatorna hänger ihop med varandra. Här är ett exempel på en inmatning:

gata Granvägen;
hus Granvägen 6A;
hus Granvägen 6B;
gata Brickebergsvägen;
hus Brickebergsvägen 19;
gata Alstavägen;
korsning Granvägen Alstavägen;
gata Barkvägen;
korsning Granvägen Barkvägen;
gata Åstadalsvägen;
gata Stenåsvägen;
korsning Åstadalsvägen Stenåsvägen Brickebergsvägen;
klart;

Här har vi matat in att det finns ett antal gator, var och en med ett namn som Granvägen eller Brickebergsvägen. Namn är alltid ett enda ord utan mellanslag, så namn som Elof Ljunggrens väg är inte tillåtna. Vi har också matat in att det finns ett antal hus, till exempel Granvägen 6A och Granvägen 6B. Dessutom anger vi hur gatorna sitter ihop med varandra i olika vägkorsningar, som att Granvägen och Barkvägen sitter ihop i en korsning. Notera att efter nyckelordet korsning kan det komma fler än två gator, exempelvis för att Åstadalsvägen, Stenåsvägen och Brickebergsvägen sitter ihop med varandra i en korsning. Hela inmatningen ska avslutas med ett klart-kommando, enligt ovan.

Inmatningen kan skrivas på fritt format. Exempelvis är nedanstående inmatning syntaktiskt ekvivalent med den i exemplet ovan.

gata Granvägen ; hus Granvägen 6A ; hus Granvägen 6B ; gata Brickebergsvägen ; hus
Brickebergsvägen 19 ; gata Alstavägen ; korsning Granvägen Alstavägen ; gata Barkvägen ;
korsning Granvägen Barkvägen ; gata Åstadalsvägen ; gata Stenåsvägen ; korsning
Åstadalsvägen Stenåsvägen Brickebergsvägen ; klart ;

Uppgift 4 (4 p)

a) Vilka terminaler behövs för att man ska kunna skriva en grammatik för språket i scenariot?

Svar:

Gatunamn, husnummer, semikolon samt nyckelorden klart, gata, hus och korsning

b) En del av terminalerna har inte fixa lexem, utan kan se olika ut. Skriv, för varje sådan terminal, ett reguljärt uttryck som beskriver hur den får se ut!

Svar:

gatunamn: [A-ZÅÄÖ][a-zåäö]*
husnummer: [1-9][0-9]*[A-Z]?

En kommentar: Beroende på implementationen, språkinställningar med mera kan det vara besvärligt att få svenska tecken att fungera i reguljära uttryck.

Uppgift 5 (5 p)

Skriv en grammatik för språket. Startsymbolen ska vara inmatning, som representerar en komplett inmatning enligt scenariot ovan.

Svar:

inmatning -> kommandon klartkommando
klartkommando -> klart semikolon
kommandon -> kommandon kommando | ingenting
kommando -> gatukommando | huskommando | korsningskommando
gatukommando -> gata gatunamn semikolon
huskommando -> hus gatunamn husnummer semikolon
korsningskommando -> korsning gatulista semikolon
gatulista -> gatunamn gatunamn flergator
flergator -> flergator gatunamn | ingenting

Uppgift 6 (4 p)

Ett parse-träd (ibland kallat "konkret syntaxträd") innehåller noder för alla icke-terminaler. Rita upp parse-trädet för den här inmatningen, enligt din grammatik i uppgiften ovan:

gata Vägen;
gata Stigen;
gata Gränden;
korsning Vägen Stigen Gränden;
klart;

Svar:

Ett parse-träd

Uppgift 7 (8 p)

Skriv en prediktiv recursive-descent-parser för språket i ett språk som åtminstone liknar C, C++, C# eller Java. Du behöver inte skriva exakt korrekt programkod, men det ska framgå vilka procedurer som finns, hur de anropar varandra, och vilka jämförelser med tokentyper som görs. Du kan anta att det finns en funktion som heter scan, som returnerar typen på nästa token, och en funktion som heter error, som man kan anropa när något gått fel och som skriver ut ett felmeddelande och avslutar programmet.

Svar:

I grammatiken som vi skrivit ovan finns två vänsterrekursioner som först måste elimineras. Vi kan skriva om grammatiken så vi får en ny grammatik som beskriver samma språk:

inmatning -> kommandon klartkommando
klartkommando -> klart semikolon
kommandon -> kommando kommandon | ingenting
kommando -> gatukommando | huskommando | korsningskommando
gatukommando -> gata gatunamn semikolon
huskommando -> hus gatunamn husnummer semikolon
korsningskommando -> korsning gatulista semikolon
gatulista -> gatunamn gatunamn flergator
flergator -> gatunamn flergator | ingenting

Ladda ner: gatuparser.c, plus en hjälpfil i form av en Lex-scannerspecifikation, gator.l, och en Bison-version av grammatiken, gator.y. Det finns också en Makefile för att bygga parsern.

Token-typer: KLART GATA HUS KORSNING GATUNAMN HUSNUMMER SEMIKOLON

void match(int expected) {
    if (lookahead != expected)
        error();
    else
        lookahead = scan();
}

void inmatning(void) {
    kommandon(); klartkommando();
}

void klartkommando(void) {
    match(KLART); match(SEMIKOLON);
}

void kommandon(void) {
    if (lookahead == GATA || lookahead == HUS || lookahead == KORSNING) {
        kommando(); kommandon();
    }
    else {
        // Ingenting
    }
}

void kommando(void) {
    if (lookahead == GATA)
        gatukommando();
    else if (lookahead == HUS)
        huskommando();
    else if (lookahead == KORSNING)
        korsningskommando();
    else
        error();
}

void gatukommando(void) {
    match(GATA); match(GATUNAMN); match(SEMIKOLON);
}

void huskommando(void) {
    match(HUS); match(GATUNAMN); match(HUSNUMMER); match(SEMIKOLON);
    }

void korsningskommando(void) {
    match(KORSNING); gatulista(); match(SEMIKOLON);
}

void gatulista(void) {
    match(GATUNAMN); match(GATUNAMN); flergator();
}

void flergator(void) {
    if (lookahead == GATUNAMN) {
        match(GATUNAMN); flergator();
    }
    else {
        // Ingenting
    }
}

void parse() {
    lookahead = scan();
    inmatning();
    printf("Ok.\n");
}

int main() {
    printf("Skriv en inmatning enligt grammatiken!!\n");
    printf("Avsluta med CTRL-D!\n");
    parse();
}


Thomas Padron-McCarthy (thomas.padron-mccarthy@oru.se) 23 januari 2024