Föreläsning 7: Syntaxanalys



Relevanta dokument
Föreläsning 7: Syntaxanalys

Reguljära uttryck Grammatiker Rekursiv nedåkning Allmänna kontextfria grammatiker. Syntaxanalys. Douglas Wikström KTH Stockholm

DD1361 Programmeringsparadigm. Formella Språk & Syntaxanalys. Per Austrin

DD1361 Programmeringsparadigm. Formella Språk & Syntaxanalys. Per Austrin

Sätt att skriva ut binärträd

Tänk på följande: Det finns en referensbok (Java) hos tentavakten som du får gå fram och läsa men inte ta tillbaka till bänken.

Dugga Datastrukturer (DAT036)

Tentamen OOP

Tentamen. 2D4135 vt 2005 Objektorienterad programmering, design och analys med Java Lördagen den 28 maj 2005 kl

Programmering för Språkteknologer II. Innehåll. Associativa datastrukturer. Associativa datastrukturer. Binär sökning.

Tentamen TEN1 HI

Algoritmanalys. Genomsnittligen behövs n/2 jämförelser vilket är proportionellt mot n, vi säger att vi har en O(n) algoritm.

i=1 c i = B och c i = a i eller c i = b i för 1 i n. Beskriv och analysera en algoritm som löser detta problem med hjälp av dynamisk programmering.

Föreläsning 7. Träd och binära sökträd

Föreläsning 13 och 14: Binära träd

Föreläsning 4: Kombinatorisk sökning

SMD 134 Objektorienterad programmering

Föreläsning 1 & 2 INTRODUKTION

Grundläggande programmering med C# 7,5 högskolepoäng

Programmering för språkteknologer II, HT2014. Rum

6 Rekursion. 6.1 Rekursionens fyra principer. 6.2 Några vanliga användningsområden för rekursion. Problem löses genom:

Tentamen i Objektorienterad programmering

Tentamen på kursen DA7351, Programmering , kl Malmö högskola Teknik och samhälle. DA7351, Programmering

Objektorienterad programmering D2

Vem är vem på kursen. Objektorienterad programvaruutveckling GU (DIT011) Kursbok Cay Horstmann: Big Java 3rd edition.

Föreläsning 6: Introduktion av listor

Tentamen för kursen Objektorienterad programvaruutveckling GU (DIT010)

Programmering A. Johan Eliasson

Objektorienterad programmering i Java

Träd, binära träd och sökträd. Koffman & Wolfgang kapitel 6, avsnitt 1 4

Automater. Matematik för språkteknologer. Mattias Nilsson

Föreläsning 10. ADT:er och datastrukturer

Tentamen i Algoritmer & Datastrukturer i Java

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 8 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Det finns en referensbok (Java) hos tentavakten som du får gå fram och läsa men inte ta tillbaka till bänken.

Dynamisk programmering

"if"-satsen. Inledande programmering med C# (1DV402)

Laboration 10 - NetBeans

TDDD02 Föreläsning 2 HT Reguljära uttryck och reguljära språk Lars Ahrenberg

Tentamen DE12, IMIT12, SYST12, ITEK11 (även öppen för övriga)

Extra övningar på SDD:er/SDT:er

Objektorienterad Programkonstruktion, DD1346 FACIT. Tentamen , kl

Kungliga Tekniska Högskolan Ämneskod 2D4134 Nada Tentamensdag aug - 23 Tentamen i Objektorientering och Java Skrivtid 5 h

trafiksimulering Intro OU5 trafiksimulering

Översikt 732G11 PROGRAMMERING 1. Personal. Kursens mål. Litteratur. Kursens innehåll

Institutionen för TENTAMEN CTH HT-15 Datavetenskap TDA540. Tentamen för TDA540 Objektorienterad programmering

Programmeringsteknik med C och Matlab

SORTERING OCH SÖKNING

Tentamen Datastrukturer D DAT 036/DIT960

Att använda pekare i. C-kod

Tentamen: Programutveckling ht 2015

Tentamen Programmeringsteknik II och NV2 (alla varianter) Skriv bara på framsidan av varje papper.

Alfabeten, strängar och språk. String

Omtentamen (del 1, 6 högskolepoäng) i Programkonstruktion och datastrukturer (1DL201)

TDDI02. Programmeringsprojekt. Föreläsning 3 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Tentamen, EDA501 Programmering M L TM W K V

Tentamen. Lösningsförslag

DD1361 Programmeringsparadigm. Formella Språk & Syntaxanalys. Per Austrin

Lösningsförslag, tentamen FYTA11 Javaprogrammering

Påminnelse: en datatyp för bilder. Programmering. En datatyp för bilder. Spegelbild. hh.se/db2004

Programmering A C# VT Ett kompendie över Programmering A (50p) i c# Stefan Fredriksson

public static void mystery(int n) { if (n > 0){ mystery(n-1); System.out.print(n * 4); mystery(n-1); } }

Föreläsning 2: Avlusning och antilustekniker

Programmeringsuppgifter 1

Introduktion till programmering. Standardfunktioner. Vad används datorer till? Standardfunktioner. Föreläsning 2. Prelude. $ ghci...

Chapter 3: Using Classes and Objects

Föreläsning 8: Intro till Komplexitetsteori

Dagens föreläsning. Repetition. Repetition - Programmering i C. Repetition - Vad C består av. Repetition Ett första C-program

7, Diskreta strukturer

Lösningsförslag till exempeltenta 1

Uppgift: Algoritm för att beräkna kontrollsiffran i ett personnummer givet de 9 första siffrorna. Torrsimning av algoritm för personnummer

Lösningsförslag till exempeltenta 2

Tentamen Programmeringsteknik II Skrivtid: Hjälpmedel: Java-bok (vilken som helst) Skriv läsligt! Använd inte rödpenna!

Föreläsning 2 Objektorienterad programmering DD1332. Typomvandling

JAVAUTVECKLING LEKTION 4

Föreläsning 5: Grafer Del 1

Datatyper och kontrollstrukturer. Skansholm: Kapitel 2) De åtta primitiva typerna. Typ Innehåll Defaultvärde Storlek

kind spelling Scanning

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

DELPROV 1 I DATAVETENSKAP

Föreläsning 11. Giriga algoritmer

Parsning = analys. Parsningsalgoritmer. Användningsområden. L1: Introduktion

Objektsamlingar i Java

IT för personligt arbete F6

DD1361 Programmeringsparadigm. Carina Edlund

Inlämningsuppgift MiniPlotter

Redovisning av inlämningsuppgifter

Flera kvantifierare Bevis Direkt bevis Motsägelse bevis Kontrapositivt bevis Fall bevis Induktionsprincipen. x y (x > 0) (y > 0) xy > 0 Domän D = R

Fortsättningskurs i programmering F 2. Algoritmer i Programutveckling Hugo Quisbert Problemexempel 1

Grundläggande logik och modellteori (5DV102)

Parsing med Recursive Descent, Avbildningsklasser. Syntaxdiagram. Syntaxdiagram och kodning expression. Betrakta följande uttryck

Exempel: Förel Rekursion III Nr 14. Uno Holmer, Chalmers,

OOP Objekt-orienterad programmering

Föreläsning 7 Innehåll. Rekursion. Rekursiv problemlösning. Rekursiv problemlösning Mönster för rekursiv algoritm. Rekursion. Rekursivt tänkande:

Editering, Kompilering och Exekvering av Javaprogram

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Föreläsning 14. Filhantering

Tentaupplägg denna gång

Tentamen, EDAA20/EDA501 Programmering

Transkript:

DD2458, Problemlösning och programmering under press Föreläsning 7: Syntaxanalys Datum: 2009-10-27 Skribent(er): Carl-Fredrik Sundlöf, Henrik Sandström, Jonas Lindmark Föreläsare: Fredrik Niemelä 1 Syntaxanalys Syntaxanalys går ut på att analysera en text och bestämma dess grammatiska struktur med avseende på en formell grammatik. Vi kan beskriva syntaxanalysen som en följd av olika steg. Följande bild beskriver de olika stegen. Textsträng (Lexikal analys) Tokenström (Syntaxanalys) Syntaxträd (Semantisk analys) Resultat Lexikal analys. Indata är en textsträng eller en textström. I det här steget delas indata upp i intressanta tokens. För t.ex. naturligt språk skulle den här delen gå ut på att dela upp en text i enskilda ord. Resultatet av det här steget representeras av en så kallad tokenström. Syntaxanalys. Målet med det här steget är att identiera de tokens vi skapade under den lexikala analysen och skapa ett syntaxträd. I exemplet med naturligt språk går det här steget ut på att grammatiskt analysera tokenströmmen och skapa grammatiska träd för meningarna i texten. Semantiskanalys. Det här steget går ut på att bearbeta resultatet av syntaxanalysen till en mer lättläslig form. Det nya resultatet är menat att modellera betydelsen eller resultatet. Steget är inte nödvändigt men är ofta användbart eftersom det underlättar tolkning av resultatet. 2 Grammatiker I syntaxanalyssteget använder vi oss av någon typ av grammatik för att kunna analysera vår tokenström. En grammatik innehåller en mängd slutsymboler och en mängd ickeslutsymboler. En slutsymbol är en sträng i indatan som inte går att bryta ned i delar utan att det förlorar sin betydelse. En ickeslutsymbol kan ses som typ i grammatiken, t.ex. substantiv för naturligt språk. I den här kursen tas kontextfria grammatiker upp. Kontextfri betyder att en regel i grammatiken inte beror av kontexten i vilken regeln appliceras. I praktiken betyder detta att vänster ledet i produktionsreglerna endast innehåller en ickeslutsymbol. Så formen för produktionsreglerna blir: 1

2 DD2458 Popup HT 2009 <NT> = S Där <NT> är en ickeslutsymbol och S är en eller era ickeslutsymboler eller slutsymboler. Det här tolkas som att om vi har S i indatan så kommer vi skapa en nod i syntaxträdet med alla delar i S som barn. På det här sättet bygger vi upp syntaxträdet med hjälp av grammatiken. 3 Rekursiv medåkning Istället för att rakt av konstruera ett syntaxträd kan vi använda oss av rekursiv medåkning. Algoritmen fungerar endast på klassen av LL(k) kontextfria grammatiker. LL(k) står för Left to right, Leftmost derivation och k'et innebär att vi behöver titta k tokens i förväg för att kunna avgöra vilken produktionsregel vi ska använda. En rekursiv medåknings implementation går i princip ut på att man konstruerar en funktion för varje ickeslutsymbol som det nns en produktionsregel för. Ofta är det så att det existerar er än en enskild produktionsregel för en viss ickeslutsymbol. Man behöver då i funktionen titta k steg i förväg för att avgöra vilken av produktionsreglerna som skall utvecklas. Avsnitt 4.3 ger ett exempel på hur rekursiv medåkning fungerar. 4 Exempel - Molekylvikt I följande exempel skall vi beräkna vikten för en molekyl givet molekylens kemiska formel samt en lista med atomvikter. Följande tabell visar atomernas vikt. Atom Vikt H 1 O 16 N 14 C 12 Molekylvikten för t.ex. H 2 O är 1 2 + 16 = 18. Vi skall nu skapa ett program som beräknar dessa vikter. Problemet är att inte alla molekyler är så snälla som H 2 O. Nitroglycerin har den kemiska formeln C 3 H 5 (NO 3 ) 3 och får då molekylvikten 12 3 + 1 5 + (14 + 16 3) 3 = 227. Så hur analyserar vi detta? 4.1 Lexikal analys Indata till programmet för C 3 H 5 (NO 3 ) 3 är på formen C3H5(NO3)3. Den lexikala analysen kommer nu göra om indata till en ström av tokens. Vi kommer att ha följande tokens:

Syntaxanalys 3 atom - En atom. En versal följt av noll eller en gemen blir en atom. int - Ett tal. lpar - En vänsterparentes. rpar - En högerparentes. $ - Slut på indata. Vissa tokens kommer vi knyta ett värde till. I detta fall kommer atom och int få ett värde knutet till sig. Indata, C 3 H 5 (NO 3 ) 3, kommer att översättas till följande ström av tokens: atom int atom int lpar atom atom int rpar int $ C 3 H 5 N O 3 3 Strömmen av tokens kan nu analyseras med hjälp av en kontextfri grammatik. 4.2 Kontextfri grammatik För att beskriva hur molekyler är uppbyggda kan vi använda oss av följande kontextfria grammatik: <start> = <molekyl>$ <molekyl> = <del> <molekyl> <molekyl> = <del> <del> = <enhet> int <del> = <enhet> <enhet> = lpar <molekyl> rpar <enhet> = atom Genom grammatiken kan vi bygga följande syntaxträd för C 3 H 5 (NO 3 ) 3.

4 DD2458 Popup HT 2009 För att få reda på värdet av molekylvikten är det nu bara att traversera trädet och beräkna vikterna allteftersom. Att först bygga trädet och sedan traversera det är tidskrävande och det är i detta fall bättre att använda sig av rekursiv medåkning för att beräkna molekylvikten. 4.3 Rekursiv medåkning Vi skapar nu funktioner för alla ickeslutsymboler i grammatiken. Vår grammatik är av typen LL(1) och vi behöver således titta på en token i förväg. Vidare förutsätter vi att det nns tre funktioner, peektoken() som kollar vad nästa token är, consume() som förbrukar en token samt slåuppatomvikt() som retunerar atomvikten för en atom. Genom att funktionerna rekursivt kallar på varandra kommer vi beräkna molekylvikten allteftersom. En token har två attribut, type och val. Följande funktioner behövs.

Syntaxanalys 5 start() (1) r = molekyl() (2) t = peektoken() (3) om t.typ = $ (4) return r (5) annars (6) FEL! molekyl() (1) r = DEL() (2) t = peektoken() (3) om t.typ = atom, lpar (4) return r + molekyl() (5) annars om t.typ = $, rpar (6) return r (7) annars (8) FEL! del() (1) r = ENHET() (2) t = peektoken() (3) om t.typ = int (4) consume() (5) return r t.val (6) annars om t.typ = atom, lpar (7) return r (8) annars (9) FEL!

6 DD2458 Popup HT 2009 ENHET() (1) t = peektoken() (2) om t.typ = atom (3) consume() (4) return slåuppatomvikt(t.val) (5) annars om t.typ = lpar (6) consume() (7) r = MOLEKYL() (8) t = peektoken() (9) om t.typ = rpar (10) consume() (11) return r (12) annars (13) FEL! (14) annars (15) FEL! 5 Cocke-Younger-Kasamis algoritm Cocke, Younger och Kasamis konstruerade en algoritm som givet en kontextfri grammatik på Chomsky Normal Form (CNF), avgöra om en given sträng går att generera med den grammatiken. För att en grammatik ska vara i CNF så måste dess produktionsregler vara på en av följande former: <l> = <r 1 ><r 2 > där <r 1 > och <r 2 > är ickeslutsymboler. <l> = x där x är en slutsymbol. Alla kontextfria grammatiker kan uttryckas i CNF. Om en regel är på formen <l> = x<r 1 > där x är en slutsymbol och <r 1 > är en ickeslutsymbol skapar vi en ny ickeslutsymbol <r 2 > och inför produktionsregeln <r 2 > = x och substituerar så vi får: <l> = <r 2 ><r 1 > <r 2 > = x På liknande sätt kan vi dela upp långa produktionsregler i mindre genom att införa nya ickeslutsymboler och substituera. När vi har grammatiken i CNF kan vi använda dynamisk programmering för att lösa problemet genom att se på delproblemet om en delsträng av en viss längd kan genereras med reglerna eller inte. Vi skapar en tredimensionell bool matris gen[i, j, k] där i är index för regeln, j är startindex för delsträngen och k är längden på delsträngen. gen[i, j, k] ska vara sann omm delsträngen som startar i index j och är k lång kan genereras med regeln

Syntaxanalys 7 i, annars falsk. Till att börja med sätts alla värden i gen till falska. Sedan går vi igenom och fyller i basfallen. Nämligen alla delsträngar av längden ett. För alla produktionsregler på formen <l> = x där x är en slutsymbol letar vi efter tecken i indatan som är lika med x och sätter dessa poster i gen till sanna. Sedan för alla möjliga längder av delsträngar börjar vi fylla i om det går att generera en delsträng av den längden genom att dela upp delsträngen med en produktionsregel på formen <l> = <r 1 ><r 2 >. Alltså, för varje längd av delsträng k, för varje startindex j och för varje uppdelning l av k som går att göra ser vi om vi kan hitta en regel <l> = <r 1 ><r 2 >. Där vi kan generera <r 1 > ur intervallet j till j + l och vi kan generera <r 2 > ur intervallet j + l till j + k, om så är fallet sätter vi gen[< l >, j, k] till sant. Följande pseudokod beskriver algoritmen: Algoritm 1: Cocke-Younger-Kasamis algoritm Cocke-Younger-Kasamis(x 1, x 2,..., x n ) (1) i, j, k (2) gen[i, j, k] = false (3) regler < a i >= S (4) tecken i strängen, (x j ) (5) om < a i >= x j (6) gen[i, j, k] = true (7) (8) for k = 2 n (9) for j = 1 n k + 1 (10) for l = 1 k 1 (11) < a i >=< a s >< a t > (12) if gen[s, j, l] och gen[t, j + l, k l] (13) gen[i, j, k] = true Komplexiteten för algoritmen är ganska uppenbart O(n 3 ) eftersom vi har tre nästlade for slingor som vardera kan köras O(n) gånger. Algoritmen är avsevärt mycket långsammare än rekursiv medåkning men fungerar på alla kontextfria grammatiker. 6 Reguljära uttryck Reguljära uttryck är en notation för att beskriva en mängd av strängar. Strängarna är uppbyggda av en mängd bokstäver från ett alfabet Σ. Ett specikt reguljärt uttryck är en sträng som följer särskilda syntaxregler. Dessa regler varierar mellan olika implementationer men alla bygger i grunden på fyra olika byggstenar. Dessa byggstenar är:

8 DD2458 Popup HT 2009 Konkatenering AB Beskriver en särskild följd av uttryck. Efter A så måste B komma. Alternativ A B Säger att antingen förekommer A eller så förekommer B. Upprepning A* Betyder att A får före komma ett godtyckligt antal gånger. A behöver inte förekomma alls. Epsilon ɛ Kommer inte konsumera någonting ur indata utan går vidare. Med dessa byggstenar så kan man bygga alla andra reguljära uttryck. I java nns det som exempel en +-operator. Sätter man denna efter en bokstav, t.ex. a+, så betyder det att bokstaven måste förekomma minst en gång men samtidigt får den förekomma hur många gånger som helst. Denna operator kan lätt beskrivas med våra byggstenar som: aa*. Några andra byggstenar som brukar nnas i de mest använda implementationerna är: Gruppering (A) Säger att A måste förekomma. Till exempel kan man ha problem med 'ö' och vilja matcha olika typer av 'ö' i ett ord. I ett ord som ström kan man göra det genom att byta ut 'ö' med str(ö o oe)m. Hakparanteser [A] Säger att någon av bokstäverna i A måste förekomma. Till exempel så kanske man vill matcha a, b eller c. Då skriver man [abc]. Hakparanteser brukar också innehålla stöd för att uttrycka spann av bokstäver. Om man vill ha alla bokstäver mellan a och z så kan man skriva [a-z]. Icke [ ˆ A] Säger att inte någon av bokstäverna i A får förekomma. Plus A+ Betyder att A måste förekomma minst en gång men får förekomma ett godtyckligt antal gånger. Frågetecken A? Betyder att A kan förekomma en gång men inte er. Förutom dessa så brukar det också nnas fördenerade grupper av bokstäver. I t.ex. Java betyder \w samma sak som [a-za-z_0-9] och \s betyder [ \t\n\x0b\f\r] (ett mellanrum av någon typ). 6.1 Reguljära uttryck som ändliga automater Om man inte har tillgång till ett färdigt bibliotek för reguljära uttryck men ändå vill evaluera en sträng med dessa så görs det bäst genom att ställa upp uttrycken som en ändlig automat. Sedan utvärderar man strängen genom att traversera automaten. De tre första byggstenarna som beskrevs tidigare ser ut på följande sätt som automater:

Syntaxanalys 9 A B A* AB 6.2 Reguljära uttryck i Java För att få tillgång till java regex så använder man sig av klasserna Pattern och Matcher. Båda dessa ligger i paketet java.util.regex. Om man vill få allmän förklaring till vilka beteckningar man kan använda så hittar man en n översikt av dessa på http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/pattern.html Nedan följer ett exempel på hur man använder Pattern och Matcher. I exemplet så kompilerar man ett regex som matchar strängar som innehåller ett eller era 'a'. import java.util.regex.matcher; import java.util.regex.pattern; public class main { public static void main(string[] args) throws Exception { Pattern p = Pattern.compile("a+"); Matcher m; m = p.matcher("aaaaaaaaa"); if (m.matches())system.out.println("good"); else System.out.println("Bad!"); } } m = p.matcher("aaaabbbb"); if (m.matches())system.out.println("good"); else System.out.println("Bad!");

10 DD2458 Popup HT 2009 Kör man programmet kommer outputen bli: Good Bad! Lite mer avancerad regex i java: [a-c]+[ ˆ def]* Matchar alla strängar som börjar med någon av bokstäverna a, b eller c en eller era gånger och sedan följs av godtycklig sträng som inte innehåller d, e eller f. Strängar som skulle matchas är: abchej, aaag, a och apa. Strängar som inte skulle matchas är: hej, abce, alfa. (hej tja hejsan)\\s+[a-z][a-z]* Matchar strängar som börjar med hej, tja eller hejsan, följt av ett eller era separeringstecken och sedan en sträng som börjar på en stor bokstav följt av ingen eller era små bokstäver. Strängar som matchas är: hej Allan, tja Urban. Strängar som inte matchas är: hej allan, halloj Urban. Om man har ett regex som inte fungerar så nns det olika verktyg för att felsöka det. Ett sådant är Regular Expression Test Page som går att nna på: http://www.regexplanet.com/simple/index.html 6.3 Reguljära uttryck i C/C++ För att få tillgång till regex i C/C++ så kan man includera <regex.h>. regex.h nns som standard i GCC. Det är inte så stor skillnad på regex i Java och i C/C++. Det som nns i Javas standardimplementation nns med största sannolikehet också i regex.h. Exemplerna i Java fungerar även om man portar dem till C/C++. Här är ett kort exempel på hur regex kan användas i C++: #include "regex.h" #include <string> #include <iostream> using namespace std; int main(){ //Sätt upp ett pattern string pattern = "[h-k]ej"; //Skapa och kompilera regex regex_t re; regcomp(&re, pattern.c_str(), REG_EXTENDED REG_NOSUB); //Se om text kan matchas string text = "hej"; if (regexec(&re, text.c_str(), (size_t)0, NULL, 0) == 0) printf("hello World!\n");

Syntaxanalys 11 else printf("bad!\n"); } return 0; Tack till Johannes Svensson för hjälpen med exemplet.