• No results found

Ett tillägg som behöver göras till L3 för att klara GDL är stöd för disjunktioner och konjunktioner, d.v.s. stöd för att hantera or och and.

rule:

LPAREN! i:IMPLICATION^ (predicate | constant_predicate) (literal_or_logical)+ RPAREN!

{#i.setType(RULE);} ;

literal_or_logical: disjunction | conjunction | literal; conjunction:

(LPAREN! AND^ literal_or_logical (literal_or_logical)+ RPAREN!); disjunction:

(LPAREN! OR^ or_child_and_clause (or_child_and_clause)+ RPAREN!);

or_child_and_clause:

conjunction | and_child_literal | and_child_or; and_child_literal: literal

{ #and_child_literal = #([AND], #and_child_literal); } ; and_child_or: disjunction

{ #and_child_or = #([AND], #and_child_or); } ;

Exempel: (<= (p a b c) (or (q a) (and (r b) (s c)))) 5.8.1 OR

När man har flera predikat med samma namn så unifierar ett predikat med det namnet om det matchar något av dem. På sätt och vis fungerar det som om det fanns ett OR förhållande mellan dem. Det är också på det viset som OR hanteras i draggeneratorn: man skapar “surrogat-predikat” för varje literal i OR och ersätter själva OR med ett anrop till dessa. Argumentet till detta surrogat-predikat är de register som används både innanför och utanför OR-satsen.

5.8. DISJUNKTIONER OCH KONJUNKTIONER

När man bryter ut koden så kan detta påverka om variablerna inne i koden ska vara permanenta. Om de bara förekommer i den utbrutna koden så behöver de inte det.

(<= (or_tester ?a) (t ?b)

(or (and (p ?a) (q ?a)) (and (r ?b ?c)

(s ?c))))

blir omvandlad till kod som motsvarar (<= (or_tester ?a ?b) (t ?d) (__or_9 ?a ?b)) (<= (__or_9 ?a ?b) (p ?a) (q ?a)) (<= (__or_9 ?a ?b) (r ?b ?c) (s ?c))

Notera att ?c inte längre förekommer i or_tester.

Förbud mot OR i förfrågningar Förfrågningar kan inte ha OR i sig. Anled-ningen till detta är att man skulle behöva modifiera själva programmet för att stoppa in och sedan ta bort surrogat-predikaten när man exekverar förfrågningar och det skiljer sig mot hur man i övrigt kan behandla programmet som read-only när man väl börjar köra det.

Detta skulle behöva göras för att man skulle kunna ha OR-satser i förfrågningar: 1. Man måste rycka ut alla surrogat-predikat som genererats, botten upp. Detta är för att hantera de fall där OR-satser i sin tur innehåller andra OR-satser. 2. Dessa måste placeras in i programmet och deras adresser hämtas ut och lagras,

både i programmet och kompilatorn, eftersom surrogat-predikat ska kunna anropas av vanliga call-instruktioner.

3. I varje steg måste man länka dessa, och då även ta med de surrogat-predikat som redan hämtats ut.

5.8.2 AND

AND fungerar som den vanliga listan av predikat i en sats eftersom en sats bara är sann om alla dess literaler i kroppen är sanna. Därför är AND för det mesta meningslöst, förutom när man grupperar barn till OR-satser. En slutsats man kan dra av ovanstående är att OR och AND tillför inte något nytt till språket.

Tänk på att distincts och NOT inuti en AND bara lägger dessa sist i just den AND. Om man har flera AND i rad så kan alltså beteendet skilja sig mot om man hade tagit bort AND:arna. Detta ska dock vara den enda skillnaden.

KAPITEL 5. KOMPILATORN

För närvarande injiceras “gömda” AND-noder mellan en OR-sats och de lite-raler som den har som barn. Anledningen till detta är för att internt underlätta kompileringen genom att samtliga barn till OR då blir AND-noder, antingen för att de var det från början eller för att de förvandlades till det.

Eftersom man lägger in gömda AND-noder som föräldrar till varje literal i en OR-sats så påverkas även distinct och not i det fallet:

(<= (broken ?x ?y)

(or (distinct ?x ?y) ; kommer INTE läggas efter positive1 (positive1 ?x ?y))

(positive2 ?x ?y)) ; kommer INTE läggas före hela OR.

5.9 Negationer

Det sista tillägget till L3 man måste göra för att det ska klara GDL är negationer. Negationer behandlas i draggeneratorn som på samma sätt som i Prolog, d.v.s. med en metod som kallas negation-as-failure. Idén är att man försöker unifiera ett predikat och om det lyckas så misslyckas not-satsen, annars är den sann. Det skulle kunna uttryckas ungefär på detta vis:

(<= not-P

(or (and (P

@fail)) @true))

Detta kommer dock inte att fungera, eftersom misslyckandet bara kommer att leda till att man backtrackar och därför hamnar i @true och så kommer ovanstående sats alltid att lyckas, vare sig P är sann eller inte. Man behöver på något vis säga till VM att man efter P inte vill backtracka och tvinga den till att slutgiltig acceptera @fail, som ju är ett misslyckande.

För att klara detta så lägger man till en speciell operator som kallas cut. cut fungerar som en uppmaning att glömma alla choice points som förekommit i en sats innan cut (inklusive en eventuell choice point på själva predikatet) och frysa varia-belbindningarna till de värden som de är nu. Om termer efter cut skulle misslyckas kommer också hela satsen att misslyckas. Däremot påverkas inte choice-points i satser som anropar not-P.

Cut-operatorn skrivs ut med ett utropstecken, vilket skulle gör att man skulle kunna skriva om not-P på följande vis:

(<= not-P

(or (and (P !

@fail)) @true))

Ovanstående är inte korrekt GDL, men kompilerar och fungerar i denna drag-generator. cut används bara indirekt av GDL genom att not-satser transformeras om till satsen med cut.

literal:

(predicate | constant_predicate | special_commands | distinct | neg_literal);

5.9. NEGATIONER special_commands: TRUE | FAIL | CUT ; neg_literal:

LPAREN! NEGATION^ o:literal_or_logical RPAREN! { #neg_literal = #([OR,"not-or"], ([AND,"and"], #o, [CUT,"cut"], [FAIL,"fail"]), ([AND,"and"], [TRUE,"true"]) ); };

En användbar funktion hos ANTLR är att den ger möjligheten att göra omvand-lingar av det genererade syntaxträd. Man kan definiera mönster i trädet som sedan matchas och omvandlas. Definitionen av neg_literal ovan genomför en sådan för omvandling av (not P) till not-P satsen som den gavs ovan.

Liksom distinct kommer not att läggas i slutet av den sats som den befinner sig i, efter alla positiva literaler, tillsammans med övriga not och distinct. Deras inbördes ordning behålls. Efter trädtransformationen ovan finns det ingen särskild not-token, då denna byts ut mot en OR. Under transformationen ger man därför den OR-noden en särskild beteckning (“not-or”) för att man ska kunna flytta den sist i satsen. Sedan kompileras den på samma sätt som en OR-nod.

Kompilering De tokens som lagts till för att hantera negationer kompileras till instruktioner på följande vis:

FAIL Omvandlas till instruktionen fail, som helt enkelt omedelbart backtrackar. TRUE Blir instruktionen true, som inte gör något alls, förutom att gå vidare till

nästa instruktion. Den finns bara till så att det finns ett alternativ att gå till i konstruktionen som används för negation, om det som ska negeras misslyckas.

CUT Kan bli två olika instruktioner beroende på var i en sats den förekommer.

Om den är den första noden så blir det en neck_cut, annars lägger man en get_level instruktion i början på satsen och lägger dit en cut instruktion på de ställen som det finns CUT-noder. Man har högst en get_level i en sats, även om det finns flera CUT-instruktioner. I ett korrekt GDL-program så kommer man aldrig att få neck_cut eftersom det enda ställe man har cuts på är i hanteringen av not, och där kommer den inte först. neck_cut är dock implementerad i denna draggenerator som ett tillägg, eftersom den stöder att man använder cut-operatorn(!) separat från not.

Det går bra att ha flera cut-operatorer i samma sats, även om en av dem är en neck_cut:

(p a) (p b)

KAPITEL 5. KOMPILATORN (p c) (q b) (q c) (r c) (<= (multi-cut ?a b) ! (p ?a) (q ?a) ! (r ?a)) (multi-cut ?a ?b)

Med ovanstående program kommer förfrågan (multi-cut c b) att lyckas, ef-tersom (p c), (q c) och (r c) är sanna. Däremot är (multi-cut d b) inte sann trots att den skulle matchas av (multi-cut ?a ?b). Anledningen till detta är neck-cut på den första multi-neck-cut som gör att det räcker med att man bara kommit in i den, vilket man ju gör då huvudet matchar, så är det det enda predikatet man bryr sig om, även om kroppen sedan misslyckas med att matcha.

(multi-cut ?a b) kommer också att misslyckas. Som i det andra fallet så mat-char huvudet på (multi-cut ?a b) och det blir därför det enda predikatet man bryr sig om, p.g.a. neck_cut. I kroppen kommer man sedan första att matcha (p ?a) mot (p a), vilket går bra med ?a bunden till a. Då kommer (q ?a) dock att miss-lyckas, då inte (q a) är sant. Men då det inte finns någon cut mellan (p ?a) och (q ?a) kan man backtracka tillbaka och testa nästa matchning på (p ?a), d.v.s. (p b). Då matchar (q b) och man går vidare över nästa cut. Nu kommer man dock få problem då (r b) inte matchar, och man får inte backtracka tillbaka över cut. Så därför misslyckas hela matchningen.

Eftersom OR-literaler bryts ut till egna predikat kommer cut-operatorer inne i dessa bara påverka själva OR.

(<= (p a)

(or (and (q a)

! ; Denna hindrar inte att (p b) testas. (r a))

(q b))

! ; ... men det gör denna.

(s a)) (p b)

Denna effekt utnyttjas i omvandlingen av not-satser.

Exekvering När man exekverar någon av neck_cut eller cut-instruktionerna ska man slänga bort alla choicepoint efter att man kom in i predikatet de befinner sig i. Det innebär att man sätter tillbaka innehållet i B, som pekar ut den nuvarande choice pointen, till det värde som den hade innan anropet av predikatet. För att hålla reda på detta värde sparar call och execute undan B i ett särskilt register, B0. Man modifierar sedan allocate och try_me_else så att de sparar undan B0 i sina respektive STACK-strukturer.

neck_cut och get_level lägger sedan helt enkelt bara tillbaka B0 i B. neck_cut gör det direkt medan get_level tar tillbaka värdet från stacken.

Related documents