Kui lähtetekst ei ole lineaarne, s.t. järjestikku täidetavate käskude jada, vaid esineb ka suunamisi (näiteks tingimuslause), tekib vajadus muuta käskude sooritamise järjekorda ka transleerimise tulemusena saadavas objektekstis, s.t. seal tuleb kasutada suunamisi GOTO, IF...GOTO. Et suunamiskäske saaks kasutada, tuleb väljundtekst varustada märgenditega; need on lihtsalt täisarvud. Märgendite genereerimiseks käivitatakse mingi alamprotseduur, mis iga uue käsu genereerimisel annab muutujale ln (järgmise käsu reanumber) uue väärtuse.
Kõige "põhilisem" suunamiskäsk on if-käsk, mis tavaliselt esineb kahes versioonis: ilma else-osata ja koos else-osaga. Kui süntaksianalüüsis kasutatakse LL(1) grammatikat, kirjeldatakse mõlemad versioonid tavaliselt koos ühe produktsioonina:
if-lause IF tingimus THEN laused (ELSE laused)? ENDIF
Vaatame algul informatsiooni liikumist if-käsu puus, kui else- osa puudub:
Puuläbija genereerib puud lõppjärjestuses läbides kõige enne tingimus-alampuule vastava koodi. Seejärel jõuab ta tingimus-alampuu tippu (ellips) ja genereerib kaks suunamiskäsku: n IF tingimus GOTO (täidetakse kui puu läbimisel arvutatud loogiline muutuja tingimus on tõene) ja n+1 GOTO ..., mis peab suunama if-käsust edasi, s.t. kogu if-käsu poolt genereeritud koodiosale järgnevale reale. Kuna pärast tingimusele vastava alampuu läbimist hakkab puuläbija töötlema then-lausetele vastavat alampuud (lõppjärjestus!), tuleb esimese, tingimusliku suunamise n IF tingimus GOTO märgendiks ilmselt n+2. Kuid järgmise käsu n+1 GOTO ... märgendit pole siin üldse veel võimalik teada, sest pole teada, kuipalju väljundteksti ridu genereeritakse then-lausete alampuu läbimisel. See info tekib alles siis, kui puuläbija jõuab then-lausetele vastava alampuu läbimise järel then-osa tippu (ellips). Kokku saab viia varem poolikuks jäänud GOTO-käsu reanumbri ja sellesse tuleva märgendi (see on muutuja ln väärtus, kui puuläbija jõuab then-osale vastava alampuu tippu) alles kogu if-lause tipus IF . Tingimusele vastava alampuu tipp saadab sinna reanumbri n+1, then-osale vastava alampuu tipp - märgendi ln ja tipp IF genereerib nüüd täieliku käsu n+1 GOTO ln .
Kui if-käsul on ka else-osa, muutub info liikumine veidi - then-osa lõppu tuleb genereerida veel üks GOTO-käsk, mis juhib else-osast üle, kogu if-lause poolt genereeritud koodile järgnevale reale. Ka selle GOTO-käsu märgendit pole then-osa lõppedes võimalik teada, sest pole teada, kui palju ridu genereeritakse else-osas. Pole isegi teada, kas seda GOTO-käsku on üldse tarvis, sest then-alampuud läbides puuläbija ei "näe", kas else-osa üldse on olemas - kui else-osa pole, pole ka GOTO-käsku tarvis, väljundkoodi täitmine läheb niigi järgmisele reale. Vajaliku info saab kokku alles else-osa läbimisel või (kui seda pole) if-lause kõige ülemises tipus IF, s.t. if-käsu transleerimise lõpetamisel. Tipp IF kontrollib else-osa tipust saadud märgendit ln (string); kui see on tühi, siis else-osa pole ja then-osa lõppu pole GOTO-käsku tarvis genereerida. Kuna selle jaoks rida (s.t. märgend) on siiski varutud, genereerivad paljud puuläbijad siiski selle GOTO-käsu, mis else-osa puudumisel juhib lihtsalt järgnevale reale.
Mõlema juhu koos käsitlemine lihtsustub, kui muuta grammatikaga puu struktuuri - viia (valikuline, s.t. võib olla, võib ka puududa) else-alampuu then-alampuu kõige ülemise tipu alla. Kuna puuläbija jõuab sellesse tippu pärast else-alampuu läbimist (kui seda pole, siis kohe pärast then-lausete alampuu läbimist), on selles tipus kohe olemas muutuja ln väärtus ja selle toimetamine IF-tippu jääb ära.
Puu teisendamiseks, s.t. else-alampuu "allanihutamiseks" tuleb teisendada ülaltoodud if-then-lauset kirjeldavat produktsiooni.
Järgnevas tekstis on abstraktse süntaksi puu (AST-puu) moodustamisel kasutatatud modifikaatoreid ^(tõstab eelneva tipu üles, s.t. teeb sellest alampuu juure) ja ! (jätab eelneva tipu ära), näiteks järgnevad reeglid genereerivad lausest IF a>b THEN x=1; ELSE y=2; ENDIF sellise abstraktse süntaksi puu:
if_lause :.
"IF"^ tingimus then_osa ;
then_osa :
"THEN"^ then_laused else_osa ;
else_osa :
ELSE^ statement ENDIF!
| ENDIF!
Tavaliselt võib AST-puu moodustamisel sisendteksti lekseemid ("if", "then", "else") ära jätta (! tipu järel näitab, et seda tippu AST-puu moodustamisel ei kasutata), kuid siin on neid tarvis, sest just nendes tippudes hakkavad toimuma AST-puu läbimisel semantilised tegevused, sellepärast nihutatakse siin need lekseemid vastavate alampuude juureks.
if_lause :
"IF"^ tingimus then_osa
; then_osa :
"THEN"^ then_laused else_osa
; tingimus :
expr (SUUREM^|VAIKSEM^|VORDNE^) expr
; then_laused :
statement ;
else_osa :
"ELSE"^ statement "ENDIF"!
"ENDIF"!
;
Märgendeid ja genereeritud käsuridu on loomulik käsitleda vastavusena märgend käsurida ja salvestada Java andmestruktuuris treemap. Kuna Java järjestab treemap-struktuurid nende indeksi (märgendi) järgi, võimaldab see lisada vajalikud GOTO-käsud hiljem (pole tarvis algul genereerida aukudega käske, kuhu märgend lisatakse hiljem). Selleks lisame puuläbija deklaratsioonidele deklaratsiooni :
public static java.util.Map codeLine = new java.util.TreeMap();
Vastavalt tuleb muuta ka väljundrida genereerivat funktsiooni:
public static java.util.Map out(int Nr, String s)
{
codeLine.put(new Integer(Nr), new String(s));
return codeLine;
};
Info saatmiseks tingimuse, then-osa ja else-osa alampuudest IF-tippu tuleb need deklareerida funktsioonideks, mis väljastavad Java String-tüüpi muutuja.
if_lause
{int nr=0; String lnr="";} :
^('IF' nr=tingimus lnr=then_osa)
{out(nr, "GOTO "+lnr+"\n");
}
;
then_osa returns [String lnr=""]
{int nr=0; String lnr1="";} :
^('THEN' nr=then_statements lnr1=else_osa)
{if (lnr1=="")
{lnr=Integer.toString(ln+1);}
else
{out(nr, "GOTO "+lnr1+"\n");
lnr = Integer.toString(nr+1);}
}
;
else_osa returns [String lnr=""] :
^('ELSE' statement)
{lnr=Integer.toString(ln+1);}
|
;
then_statements returns [int nr=0] :
statement
{nr=getLineNumber();
}
; tingimus returns [int nr=0]
{ String e1 = "", e2=""; int n=0;} :
^(SUUREM e1=expr e2=expr)
{n=getLineNumber();
out(n,"IF "+e1+">"+e2+" GOTO "+(n+2)+"\n");
nr=getLineNumber();
}
|^(VAIKSEM e1=expr e2=expr)
{n=getLineNumber();
out(n,"IF "+e1+"<"+e2+" GOTO "+(n+2)+"\n");
nr=getLineNumber();
}
; omist { String e = ""; } :
^(OMIST id:ID e=expr)
{ out(getLineNumber(),getomist((String)id.getText(),e)); }
;
Mõnikord on tarvis puud rohkem modifitseerida, näiteks lisada uusi
tippe või muuta tippude järjekorda. Seda saab teha, näidates
süntaksigrammatikas AST-puu struktuuri operaatori -> abil (selle kasutamisel ei või
samas produktsioonis kasutada modifikaatoreid ! ^). Kui näiteks tingimusoperaatori (tingimus)?then_osa : else_osa jaoks
tahetakse analoogilist puud, tuleb süntaksigrammatika
tokens-sektsioonis koos tavalises IF-lauses kasutatava lekseemiga IF deklareerida n.õ. "imaginaarne"
lekseem IM_IF (token, mida sisendtekstis pole):
tokens {ja kirjeldada puu moodustamine produktsioonidega (operaatori -> abil puu kirjelduses, s.t. ^ järel esitatakse kõige enne juur, siis selle alluvad):
IF='IF';
IM_IF;
...
}
if_lause : IF^ tingimus then_osa
|'('tingimus')''?' then_osa -> ^(IM_IF tingimus then_osa)
...
Selliste uute "imaginaarsete" tippude lisamisega võib moodustada mõlema if-lause vormi jaoks sama struktuuriga puud, s.t. saab kasutada samu semantikareegleid. Näiteks lausetest
IF a>b THEN x=1; ELSE y=2; ENDIFmoodustatakse samasuguse struktuuriga puud (need on väljastatud peapgrogrammile puu tekstina esitamise käsu System.out.println(t.toStringTree()); lisamisega, allesitatud tekstis välja kommenteeritud):
(a>b)?x=1:y=2;
(IF (> a b) (THEN (= x 1) (ELSE (= y 2))))
(IM_IF (> a b) (IM_THEN (= x 1) (IM_ELSE (= y 2))))
Järgnevas on if-lause AST-puud moodustav grammatika Avald.g. Programmi tekstis esinevad transleeritava programmeerimiskeele reserveeritud sõnad IF, THEN, ELSE, ENDIF on kirjeldatud osas tokens - siis proovib Antlr mingi tähejada (näit identifikaatori) puhul kasutada kõigepealt neid. Et programmi lõpus (kui lõpus on mingi if-lause) ei tekiks suunamist olematule reale, genereeritakse kogu programmi lõppu käsk END. Kuna mitteterminal programm on tegelikult käskude blokk ja esineb ka if-lauses ja tsüklis (kuid nendesse pole käsku END tarvis), on lisatud vaheaste: mitteterminal programm genereerib käsu END ja teiseneb mitteterminaliks programm1, mida seejärel kasutatakse if-lauses ja tsüklis. :
grammar Avald;
options {
output=AST;
ASTLabelType=CommonTree; // type of $stat.tree ref etc...
}
tokens {
IF='IF';
THEN = 'THEN';
ELSE = 'ELSE';
ENDIF = 'ENDIF';
}
prog : prog1;
prog1: ( statement (NEWLINE!)* )+ ;
statement
: omist SEMICOLON!
| if_lause SEMICOLON!
| blokk
;
omist: ID '='^ avald //-> ^('=' ID expr)
;
avald: yksl (('+'^|'-'^) yksl)*
;
yksl
: tegur ('*'^ tegur)*
;
tegur: INT
| ID
| '('! avald ')'!
;
if_lause : IF^ tingimus then_osa
;
then_osa :
THEN^ then_laused else_osa
;
tingimus:
avald (SUUREM^|VAIKSEM^|VORDNE^) avald
;
then_laused:
statement
;
else_osa :
ELSE^ statement ENDIF!
| ENDIF!
;
blokk : '{'! statement* '}'!;
ID : ('a'..'z'|'A'..'Z')+ ;
INT : '0'..'9'+ ;
SEMICOLON : ';';
SUUREM : '>';
VAIKSEM : '<';
VORDNE : '==';
NEWLINE:'\r'? '\n' ;
WS : (' '|'\t')+ {skip();} ;
Väljundkoodi genereerib puuläbija, mis on kirjeldatud puugrammatikaga Eval.g:
tree grammar Eval;
options {
tokenVocab=Avald;
ASTLabelType=CommonTree;
}
@header {
import java.util.TreeMap;
}
@members {
public static int i = 0; // variables counter
public static int ln = 0; // line number
public static int label = 0; // label number
public static int n = 0;
public static java.util.Map codeLine = new java.util.TreeMap();
public static String getTempVariable()
{
return ("T"+(i++));
};
public static int getLineNumber()
{
return (++ln);
};
public static java.util.Map out(int Nr,String s)
{
codeLine.put(new Integer(Nr), new String(s));
return codeLine;
};
public static String getExpr(String var1, String left, String op, String right)
{
return (var1 + ":=" + left + op + right);
};
public static String getomist(String left, String right)
{
return ( left + ":=" + right);
};
}
prog : prog1 {out(getLineNumber(),"END");};
prog1: statement+ ;
statement: omist
| if_lause
;
omist: ^('=' ID e=avald)
{out(getLineNumber(),getomist($ID.text,e));}
;
avald returns [String s]
: ^('+' a=avald b=avald) { s=getTempVariable();
out(getLineNumber(),getExpr(s,a,"+",b));}
| ^('-' a=avald b=avald) { s=getTempVariable();
out(getLineNumber(),getExpr(s,a,"-",b));}
| ^('*' a=avald b=avald) { s=getTempVariable();
out(getLineNumber(),getExpr(s,a,"*",b));}
| ID
{
s = (String)($ID.text);
}
| INT {s = (String)($INT.text);}
;
if_lause :
^(IF nr=tingimus lnr=then_osa)
{out(nr, "GOTO "+lnr);}
;
then_osa returns [String lnr] :
^(THEN nr=then_statements lnr1=else_osa)
{if (lnr1=="")
{lnr=Integer.toString(ln+1);}
else
{out(nr, "GOTO "+lnr1);
lnr = Integer.toString(nr+1);}
}
;
else_osa returns [String lnr=""]:
^(ELSE prog1)
{lnr=Integer.toString(ln+1);}
|
;
then_statements returns [int nr]:
prog1
{nr=getLineNumber();}
;
tingimus returns [int nr] :
^(SUUREM e1=avald e2=avald)
{n=getLineNumber();
out(n,"IF "+e1+">"+e2+" GOTO "+(n+2));
nr=getLineNumber();}
|^(VAIKSEM e1=avald e2=avald)
{n=getLineNumber();
out(n,"IF "+e1+"<"+e2+" GOTO "+(n+2));
nr=getLineNumber(); }
|^(VORDNE e1=avald e2=avald)
{n=getLineNumber();
out(n,"IF "+e1+"=="+e2+" GOTO "+(n+2));
nr=getLineNumber();}
;
Peaprogrammis peab nüüd muutuja tulemus asemel välja trükkima Codeline:
import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import java.util.Iterator;
import java.io.*;
public class Generate {
public static void main(String[] args) throws Exception {
ANTLRInputStream input = new ANTLRInputStream(System.in);
AvaldLexer lexer = new AvaldLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
AvaldParser parser = new AvaldParser(tokens);
AvaldParser.prog_return r = parser.prog();
// walk resulting tree
CommonTree t = (CommonTree)r.getTree();
//puu väljatrykk:
//System.out.println(t.toStringTree());
CommonTreeNodeStream nodes = new CommonTreeNodeStream(t);
Eval walker = new Eval(nodes);
walker.prog();
for (Iterator it= walker.codeLine.keySet().iterator(); it.hasNext( ); ) {
Object ckey = it.next();
System.out.println(ckey+":\t "+ walker.codeLine.get(ckey));}
}
}
Loome väikese testifaili test.txt, kus on mitmesuguseid if-then-else vorme:
IF a==b THEN IF c>d THEN {x=1;y=2+3*4;}ENDIF; ELSE z=0; ENDIF;
IF a<b THEN {Y = 2*(5-2);} ELSE X = 3*4 + 5; ENDIF;
Käivitame:
java Generate < test.txt
Tulemus :
1: IF a==b GOTO 3
2: GOTO 11
3: IF c>d GOTO 5
4: GOTO 10
5: x:=1
6: T0:=3*4
7: T1:=2+T0
8: y:=T1
10: GOTO 12
11: z:=0
12: IF a< GOTO 14
13: GOTO 18
14: T2:=5-2
15: T3:=2*T2
16: Y:=T3
17: GOTO 21
18: T4:=3*4
19: T5:=T4+5
20: X:=T5
21: END
(a>b)?x=1:z=2;s.t. tingimus peab olema sulgudes, ? ja : vahel ning pärast : on kas üks käsk või blokk, kusjuures then-osas, s.t. ? ja : vahel olev käsk võib olla lõpumärgendita (selle lõppetab : ), kuid else osas (: järel) oleval käsul peab olema lõpumärgend ; (või kasutatakse seal blokki). Koosta sellise if-käsu transleerimisskeem.
(a<b)?{x=1;y=2;}:{u=1;v=2;};
5. Moodusta transleerimiskeem case-lause jaoks, selle süntaks peaks olema selline, et see transleeriks (näiteks) sellise näite
case Inflation isSiin when järel täidetavad osad on süntaksiga (statement ';'|Ident ';')+
when < 1 => Don't_worry; Be_Happy ;
when 1..2 => Start_thinking ;
when 3..4 => Convert_all_to_euro; Change_Government;
when > 4 => Sell_Stock; Move_abroad;
when others => Self_Destruct;
end case;