sats terminal Kompilatorteknik 2015 källprogram! Exempel program let var v : integer; const t ~ 3 in v := v + t kind spelling Scanning LET let föreläsningsfilmer Scannerns eller den lexikala analysatorns uppgift är att transformer en ström av tecken till en ström av token, dvs. de enskilda tecknen i källprogrammet skall grupperas till symbolerna i språket. Förutom token innehåller källprogrammet separatorer - kommentarer, blanktecken, eol,... vars ända uppgift är att skilja åt token och göra programmet lättläst. 6 (1/4) VAR var IDENTIFIER v COLON : IDENTIFIER integer... Token ::= Identifier... eot Separator ::= Comment... De regler som bestämmer hur språkets token konstrueras ur de enskilda tecknen kan ses som ett subspråk där källspråkets token utgör subspråkets satser källspråkets tecken utgör subspråkets terminala symboler För ett sådant språk kan en reguljär grammatik (grammatik som ej uppvisar self-embedding) - lexikal grammatik - konstrueras. I en lexikal grammatik (specifiserar källspråkets lexikon som ingår i språkets konkreta grammatik) består de terminala symbolerna av tecken och de icke-terminala symolerna inkluderar Token och Separator. Scannern skall, liksom parsern, leta fram början och slutet på fraser/satser. Scanning är enklare än parsing. Varför? Mini-Triangel lexikon Operator ::= + - * / < > = \ Identifier ::= Letter Identifier Letter Identifier Digit Integer-Literal ::= Digit Integer-Literal Digit Letter ::= a b... z Digit ::= 0 1 2 3 4 5 6 7 8 9 Comment ::=! Graphics eol Graphics ::= space Grapics synligt_tecken Grapics
föreläsningsfilmer 6 (2/4) Representation av token En token representeras av ett objekt med två fält: token typ - kind representeras som en konstant token stavning - spelling representeras som en teckensträng IDENTIFIER integer Token.kind Token.spelling public class Token { public byte kind; public String spelling; public Token (byte kind, String spelling) { this.kind = kind; this.spelling = spelling; public final static byte IDENTIFIER = 0, INTLITERAL = 1, OPERATOR = 2, BEGIN = 3, // begin CONST = 4, // const DO = 5, // do ELSE = 6, // else END = 7, // end IF = 8, // if IN = 9, // in LET = 10, // let THEN = 11, // then VAR = 12, // var WHILE = 13, // while SEMICOLON = 14, // ; COLON = 15, // : BECOMES = 16, // := IS = 17, // ~ LPAREN = 18, // ( RPAREN = 19, // ) EOT = 20; // end of text Obs! Ej slutlig verson Token sekvensen utgör gränssnittet mellan parsern och scannern och behöver ej lagras explisit eftersom toknarna ändast skall presenteras för parsern i ordningsföljd, varvid informationen överför till AST-trädet. public abstract class Terminal extends AST { public class String spelling;...
föreläsningsfilmer 6 (3/4) Systematisk utveckling av en scanner Scannern är mycket lik parsern och kan utvecklas på likartat sätt: 1. Uttryck den lexikala grammatiken i EBNF. Behövliga transformationer görs så att grammatiken är reguljär med ömsesidigt uteslutande startsymboler för alternativen. Substituera för att minska antalet produktioner. 2. Överför varje EBNF produktionsregel N ::= X till en scanning metod scann vars kropp bestäms av X. 3. Sammanställ scannern av En privat variabel currentchar samt de privata variabler currentkind och currentspelling De privata scan metoderna utvecklade i steg 2 utökade så att de returnerar tokens kind. Privata hjälpmetoder take och takeit. En public scan metod som avlägsnar alla separatorer före föjande token samt skapar och returnerar token. Separatorer får fritt insättas var som helst i program. dvs.: Program ::= (Separator* Token-utom-eot)* eot Alla icke-terminaler kan substitueras i en regulär grammatik. Ju färre icke-terminaler destu färre scanmetoder.
6.1 Mini-Triangles lexikala grammatik:? Nyckelorden saknas Mini triangel lexikon Token ::= Identifier Integer-Literal Operator ; : := ~ ( ) eot Separator ::= Comment space eol Operator ::= + - * / < > = \ Identifier ::= Letter Identifier Letter Identifier Digit Integer-Literal ::= Digit Integer-Literal Digit Letter ::= a b... z Digit ::= 0 1 2 3 4 5 6 7 8 9 Comment ::=! Graphics eol Graphics ::= ε space Graphics synligt_tecken Graphics space står för blanktecken, eol för tecknet för radslut och eot för endof-text. Grammatiken skiljer ej mellan identifierare och nyckelord, med scannern skall bilda olika tokentyper för dem. Klassificeringen görs i Token-klassens konstruktor. public class Token { Om tokens kind är identifier, undersöks om stavningen är lika med något av nyckelordens spelling. Om så är fallet ändras kind till ifrågavarande nyckelords värde. public byte kind; public String spelling; public Token (byte kind, String spelling) { this.kind = kind; this.spelling = spelling; if (kind == IDENTIFIER) for (int k = BEGIN; k <= WHILE; k++) if (spelling.equals(spellings[k])) { this.kind = (byte)k; break; public final static byte IDENTIFIER = 0, INTLITERAL = 1,... RPAREN = 19, // ) EOT = 20; // end of text private final static String[] spellings = { <identifier>, <integer-literal>, <operator>, begin, const, do, else, end, if, in, let, then, var, while, ;, :, :=, ~, (, ), <eot>
6.2 Steg 1: Uttryck grammatiken i EBNF och gör behövliga transformationer Är grammatiken reguljär? - nej ty produktionsreglerna för identifier, Integer-Literal och Graphics är rekursiva Identifier ::= Letter Identifier Letter Identifier Digit Integer-Literal ::= Digit Integer-Literal Digit Graphics ::= ε space Graphics synligt_tecken Graphics Eliminera rekursion en reguljär grammatik innehåller ej self-embedding. Token ::= Identifier Integer-Literal Operator ; : := ~ ( ) eot Separator ::= Comment space eol Operator ::= + - * / < > = \ Identifier ::= Letter ( Letter Digit )* Integer-Literal ::= Digit Digit* Letter ::= a b... z Digit ::= 0 1 2 3 4 5 6 7 8 9 Comment ::=! Graphics eol Graphics ::= (space synligt_tecken )* En implementering av en scanner blir enklare destu färre produktioner som ingår i grammatiken: Eliminera alla icke-terminaler utom Token och Separator genom substitution Token ::= Letter ( Letter Digit )* Digit Digit* Operator ; : := ~ ( ) eot Separator ::=! (space synligt_tecken )* eol space eol Operator ::= + - * / < > = \ Letter ::= a b... z Digit ::= 0 1 2 3 4 5 6 7 8 9 Token ::= (a b... z ) ( a b... z 0 1 2 3 4 5 6 7 8 9 )* (0 1 2 3 4 5 6 7 8 9) (0 1 2 3 4 5 6 7 8 9)* + - * / < > = \ ; : := ~ ( ) eot Separator ::=! ( space A B... all synliga tecken )* eol space eol
6.3 Token ::= (a b... z ) ( a b... z 0 1 2 3 4 5 6 7 8 9 )* (0 1 2 3 4 5 6 7 8 9) (0 1 2 3 4 5 6 7 8 9)* + - * / < > = \ ; : := ~ ( ) eot Separator::=! ( space A B... all synliga tecken )* eol space eol Är startsymbolerna för alternativen ömsesidigt uteslutande? - nej två alternativ startar med kolon Vänster faktorisera : := Token ::= (a b... z ) ( a b... z 0 1... 9 )* (0 1... 9) (0 1... 9)* + - * / < > = \ ; : ( ε = ) ~ ( ) eot Separator::=! ( space A B... all synliga tecken )* eol space eol *
6.4 Steg 2: Överför varje produktionsregel N ::= X till en scanning metod scann private void scantoken (){ Token ::= switch (currentchar) { case a : case b : case c :... case y : case z : Letter while (isletter(currentchar) isdigit(currentchar)) ( Letter Digit break; )* case 0 : case 1 : case 2 :... case 8 : case 9 : Digit while (isdigit(currentchar)) Digit* break; case + : case - : case * : case / : case < : case > : case = : case \ : case ; : case ~ : case ( : case ) : + - * / < > break; = \ ; ~ ( ) case : : : switch (currentchar) ( case = : = break; default: break; ε ) case \000 : break;bakeit(); eot default: raportera ett lexikalt fel ) private void scanseparator (){ Separator ::= switch (currentchar) { case! : {! while (isgraphic(currentchar)) Graphic* take( \n ); eol break; case : case \n : space eol break; )*
6.5 Steg 3: Scan metoderna utvecklade i steg 2 utökas så att de returnerar tokens kind och spelling. Obs! Tokensekvensen behöver ej explisit lagras private byte scantoken (){ Token ::= switch (currentchar) { case a : case b : case c :... case y : case z : Letter while (isletter(currentchar) isdigit(currentchar)) ( return Token.IDENTIFIER; )* case 0 : case 1 : case 2 :... case 8 : case 9 : Digit while (isdigit(currentchar)) Letter Digit Digit* return Token.INTLITERAL; case + : case - : case * : case / : case < : case > : case = : case \ : + - * / < > \ = return Token.OPERATOR; case ; : ; return Token.SEMICOLON; case ~ : ~ return Token.IS; case ( : ( return Token.LPAREN; case ) : ) return Token.RPAREN; case : : : switch (currentchar) ( case = : = return Token.BECOMES; default: return Token.COLON; ε ) case \000 : return Token.EOT; default: raportera ett lexikalt fel ) eot Nyckelordenas kind blir IDENTI- FIER.
6.6 Sammanställ scannern: public class Scanner { private char currentchar = källprogrammets första tecknen; private byte currentkind; private StringBuffer currentspelling; private void take(char expectedchar) { if (currentchar == expectedchar) { currentspelling.append(currentchar); currentchar = källprogrammets nästa tecken; else raportera lexikal fel private void takeit() { currentspelling.append(currentchar); currentchar = nästa tecknet i källprogrammet; private boolean isdigit(char c) {... private boolean isletter(char c) {... private boolean isgraphic(char c) {... private byte scantoken() {... private void scanseparator() {... public Token scan() { returnerar nästa token while (currentchar ==! currentchar == currentchar == \n ) ( scanseparator(); Separator )* currentspelling = new StringBuffer(""); currentkind = scantoken(); Token return new Token((byte)currentKind, currentspelling.tostring()); Sammanställ scannern av Privata variabler currentchar, currentkind och currentspelling De privata scan metoderna utvecklade i steg 2 utökade så att de returnerar tokens kind. Privata hjälpmetoder take och takeit. En public scan metod som avlägsnar alla separatorer före föjande token samt skapar och returnerar denna. ( scannar Separator*Token ) Program ::=(Separator*Token-utom-eot)* eot