• No results found

Kravet på att variabler i distinct måste förekomma i positiva literaler i samma sats säger ingenting om var i satsen dessa literaler finns. De kan alltså komma efter distinct. I draggeneratorn exekveras literalerna i den ordning som de ges, och i så fall skulle variablerna fortfarande kunna vara obundna. För att hindra detta stuvas ordningen på literalerna om så att distinct ligger sist. Om det finns flera distinct i en sats bibehålls deras inbördes ordning.

Det distinct gör är att den försöker unifiera sina båda termer. Om detta miss-lyckas så ser man den som sann. De bindningar av variabler som man gör under unifieringen kommer inte att behållas efter att man anropat distinct.

5.7 L3 - Relationer

Man kan i L3 ha flera predikat med samma namn. Detta innebär att om man skulle misslyckas med att unifiera ett delmål med en given sats eller ett givet faktum så kan det finnas andra satser eller fakta, med samma namn, som kan göra det. Man behöver alltså lägga till “backtracking”, en mekanism för att ta tillbaka de variabelbindningar som man gjorde i det misslyckade unifieringsförsöket och försöka igen med en annan sats.

Språket L3 motsvarar något som skulle kunna kallas en minimal implementa-tion av Prolog. L3 har i ursprungsformat egentligen inga grammatiska skillnader mot L2, bara semantiska. För att kunna hantera hela GDL kommer dock flera nya instruktioner att läggas till.

5.7.1 try-instruktioner

Som redan nämnts så kan den ordning man försöker unifiera satser som tillhör sam-ma predikatnamn spela roll, både för prestanda och lösbarhet. För att sam-man som programmerare ska kunna förutse sådana problem används i Prolog konventionen att man testar satserna i den ordning som de kommer i programmet. Denna kon-vention följs även i denna GDL-kompilator, även om GDL i sig inte kräver detta.

Det finns inga krav på att alla satser som hör till ett viss predikatnamn mås-te komma i följd. Man kan blanda olika predikat fritt, även om det inmås-te är att rekommendera för läsbarhetens skull.

För att tillmötesgå ovanstående krav under kompileringen av programmet så använder man sig av en HashMap som mappar mellan predikatnamn och listor med kompilerad, men olänkad kod. Man går sedan igenom programmet från början till slut, kompilerar varje sats för sig och lägger in dem i kodbiblioteket i den ordning de stöts på. När man nått slutet av programmet och ska hämta ut den färdiga koden, “klistrar” man ihop alla kodfragment som hör till varje predikat, var för sig, och lägger eventuellt till instruktioner för att hantera backtracking beroende på hur många kodfragment det var:

1 Här gör man precis som förut, inga nya instruktioner behövs.

2 Innan det första kodfragmentet lägger man till en try_me_else-instruktion och

innan den sista en trust_me-instruktion.

3 eller fler Här gör man som i fallet med 2 kodfragment, men man lägger dessutom

till retry_me_else-instruktioner före alla kodfragment förutom den första eller sista.

try_me_else och retry_me_else tar den relativa adressen till nästa kodfrag-ment som enda argukodfrag-ment, d.v.s storleken på det kodfragkodfrag-ment de läggs innan.

KAPITEL 5. KOMPILATORN

Efter att man gjort detta är kodgenereringen klar och då man nu vet storle-ken på varje predikat kan man nu sätta ihop alla predikaten till ett program och länka. Startadressen till varje predikat är den där try-instruktionen ligger om så-dan finns, eller bara den vanliga starten på predikatskoden om man inte behöver try_me_else/retry_me_else/trust_me.

(Del av) Program: (p a) (q a) (q b) (r a) (r b) (r c) Kod: p/1 91 : get_constant a/0, A1 92 : proceed q/1 120 : try_me_else 123 121 : get_constant a/0, A1 122 : proceed 123 : trust_me 124 : get_constant b/0, A1 125 : proceed r/1 106 : try_me_else 109 107 : get_constant a/0, A1 108 : proceed 109 : retry_me_else 112 110 : get_constant b/0, A1 111 : proceed 112 : trust_me 113 : get_constant c/0, A1 114 : proceed

choice points, B pekare Det try_me_else/retry_me_else/trust_me gör är att skapa / ändra / ta bort en datastruktur på STACK som kallas en choice point. Man lägger också till ett register som pekar på aktuell choice point, B registret.

I en choice point, lagras A-registren, en pekare till nästa möjliga proceduranrop, nuvarande innehåll i H,B,E och CP registren och ett nytt register som heter TR som strax kommer att gås igenom, se figur 5.9. P+l pekar på nästa “choice”, där l är ett argument till try_me_else / retry_me_else instruktionerna.

Notera alltså att både choicepoints och environments ligger på STACK. Man modifierar därmed allocate för att hantera att storleken på den struktur som ligger överst på stacken beror på om det är en choicepoint eller en environment frame, och att man alltid allokerar efter den av E och B som är störst. Man behöver inte ta hänsyn till detta i deallocate och retry eftersom båda sorters strukturer har länkar tillbaka till respektive föregående struktur av samma typ.

Att man alltid allokerar en environment frame efter den senaste choice point innebär också att denna skyddar de enviroment frame som fanns innan choice point

5.7. L3 - RELATIONER A1 n = Antal A-register An ... E CP B P+l TR H

Figur 5.9. Choice point

skapades från att skrivas över. De kan fortfarande bli inaktiva via deallocates, men dess innehåll skrivs aldrig över så länge choice point finns kvar. Det innebär att det E-värde som lagrades i choice point alltid pekar på data som det såg ut vid choice point skapande.

try_me_else skapar bara en choiceoint och fyller den med de värden som de olika registren har. Pekare till nästa möjliga proceduranrop hämtas från argumentet till instruktionen.

retry_me_else läser bara in de undansparade värdena, löser upp variabler som bundits sedan senaste try_me_else:n eller retry_me_else:n och uppdaterar peka-ren till nästa proceduranrop.

5.7.2 backtracking

Det som kan misslyckas är dels anrop till predikat som inte finns och dels misslyc-kade unifieringar. När ett sådant sker så kollar man om det finns en aktiv choice point (om B > 0) och i så fall hoppar man till nästa möjliga proceduranrop (som finns lagrat i choice pointen) som alltid kommer att vara en retry_me_else el-ler trust_me instruktion. Om det inte finns någon aktiv choice point så har man misslyckats med unifieringen, och hela förfrågan misslyckas.

TRAIL, TR pekare, HB instruktionerna retry_me_else och trust_me ska bl.a. plocka bort bindningar man gjort för variabler sedan man var inne i choice pointen senast. För att veta vilka variabler det är så lägger man till ett dataområde som kallas TRAIL som fungerar som en stack. Varje gång man binder en variabel (med bind funktionen) så lägger man in dess adress överst på stacken.

Toppen av stacken pekas ut av innehållet i ett register som heter TR, och den sparas undan i choice pointen när denna skapas. Sedan är det bara att lösa upp alla variabler mellan nuvarande TR och det undansparade värdet i choice pointen i början på retry_me\else och trust_me.

Man behöver bara spåra ändringar i variabler som fanns innan choice pointen skapades. Variabler på STACK eller HEAP som skapats efteråt ska bara tas bort för att tidigare state ska återskapas. Det innebär att man inte spårar variabler på STACK som ligger högre upp än B. För HEAP gäller det att komma ihåg hur H såg ut när den aktuella choice pointen skapades. Detta lagrar man undan i ett nytt register, som kallas HB. När man deallokerar en choice point sätts HB tillbaka till det H som förgående choice point hade.

KAPITEL 5. KOMPILATORN

Resultat Man hämtar ut resultatet på samma sätt som i L2, d.v.s. man gör alla register i förfrågan till permanenta för att kunna läsa av deras värden.

I L3 kan man få flera svar på en förfrågan. Om man efter att ha läst ut ett svar anropar backtrack så får man ut nästa svar. Genom att göra detta tills unifieringen misslyckas kan man läsa ut alla möjliga svar på förfrågan.

Related documents