1 / 34

CIn / UFPE

CIn / UFPE. Parsing. Tarciana Dias / Gustavo Carvalho tds@cin.ufpe.br / ghpc@cin.ufpe.br Março 2012. Roteiro. Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências. Processo de Compilação. Compilador (1 ou N passos).

ziv
Download Presentation

CIn / UFPE

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. CIn / UFPE Parsing Tarciana Dias / Gustavo Carvalho tds@cin.ufpe.br / ghpc@cin.ufpe.br Março 2012

  2. Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências

  3. Processo de Compilação Compilador (1 ou N passos) Análise Léxica (Scanning) Erro Tokens Análise Sintática (Parsing) Erro AST Análise Semântica Front-end Erro AST decorada Ger. Código Intermediário Back-end Cód. Interm. Otimização Cód. Interm. Geração de Código Otimização do Cód. Gerado Cód. Interm. Otimizado Cód. Objeto Cód. Objeto Otimizado

  4. Processo de Interpretação Interpretador • Fetch => Analyze => Execute • Saídas de forma imediata • Não traduz programa fonte para código objeto (a priori) • Ideal (melhor desempenho): instruções com formato simplificado (bytecode) Análise Léxica (Scanning) Erro Tokens Análise Sintática (Parsing) Erro Análise Semântica Front-end Erro Execução Ger. Código Intermediário Back-end Otimização Cód. Interm. Geração de Código Otimização do Cód. Gerado

  5. Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências

  6. Conceitos Básicos Gramáticas livres de contexto (GLC) Conjunto finito de símbolos não-terminais (V) Uma classe particular de frases de uma linguagem Ex.: Programa, Expressao, Valor Conjunto finito de símbolos terminais (Σ), disjunto de V Símbolos atômicos Ex.: ‘23’, ‘+’, ‘-‘, ‘and’ Conjunto finito de regras de produção (R) Símbolo inicial (um dos símbolos de V) (S) Ex.: Programa

  7. Conceitos Básicos Exemplo Terminais +, -, not, length, and, or, ==, ++, 0, …, 9, a, …, z, A, …, Z Não-terminais Programa, Expressao, Valor, ExpUnaria, ExpBinaria, ValorConcreto, ValorInteiro, ValorBooleano, ValorString Produções Programa ::= Expressao Expressao ::= Valor | ExpUnaria | ExpBinaria Valor ::= ValorConcreto ValorConcreto ::= ValorInteiro | ValorBooleano | ValorString ExpUnaria ::= "-" Expressao | "not" Expressao | "length" Expressao ExpBinaria ::= Expressao "+" Expressao | Expressao "-" Expressao | Expressao "and" Expressao | Expressao "or" Expressao | Expressao "==" Expressao | Expressao "++" Expressao ValorInteiro ::= [1-9] [0-9]* ValorBooleano ::= "true" | "false" ValorString ::= ([A-Za-z] | [0-9])*

  8. Conceitos Básicos Uma árvore sintática para uma gramática G Árvore com labels em que: As folhas são símbolos terminais Os nós são símbolos não-terminais Uma frase de G Seqüência de terminais de uma árvore sintática (esquerda p/ direita) Exemplo: 2 + 3 (onde o todo é 2 + 3 + 5) Uma sentença Frase cuja árvore começa a partir do símbolo inicial Exemplo: 2 + 3 (onde o todo é 2 + 3) Linguagem gerada por G: todas as sentenças de G

  9. Conceitos Básicos Gramáticas ambíguas Podem gerar 2 árvores distintas para a mesma expressão Exemplo: Suponha que usemos um único não-terminal string e que não façamos distinção entre dígitos e listas: Expressão: 9 – 5 + 2 string → string + string | string – string | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 9 – (5 + 2) (9 – 5 ) + 2 string string string string string string + - string string string string 2 9 - 5 + 2 5 9

  10. Conceitos Básicos Expressões Regulares (REs) Notação conveniente para expressar um conjunto de strings de símbolos terminais. ‘|’ separa alternativas; ‘*’ indica que o item anterior pode ser repetido 0 ou mais vezes; ‘(’ e ‘)’ são parênteses agrupadores. Exemplos: Mr | Ms gera {Mr, Ms} M(r | s) gera {Mr, Ms} ps*t gera {pt, pst, psst, pssst, ...} ba(na)* gera {ba, bana, banana, bananana, ...} M(r | s)* gera {M, Mr, Ms, Mrr, Mrs, Msr, Mss, Mrrr, ...} Assim RE são capazes de gerar linguagens simples, chamadas de linguagens regulares

  11. Conceitos Básicos No entanto, linguagens de programação completas são self-embedding Por exemplo, as expressões representadas por LE1 ou LE2 (vistas em sala de aula), ex.: a * (b+c) / d contém outra subexpressão embedded, (b + c). O comando if x > y then m := x else m := y contém o subcomando embedded, ‘m := x’ Portanto, self-embedding nos permitem escrever expressões complexas, comandos, etc; Uma linguagem regular, ou seja, que não exibe self-embedding pode ser representada através de REs. Já uma linguagem que exiba self-embedding não pode ser gerada por REs. Para isso, nós escrevemos regras de produção recursivas usando ou BNF ou EBNF.

  12. Conceitos Básicos EBNF é uma combinação de BNF com REs. Uma produção EBNF é da forma N := X, onde N é um símbolo não-terminal e X é uma RE estendida, ou seja, uma RE construída a partir de ambos os símbolos terminais e não-terminais. Diferentemente de uma BNF, o lado direito de uma produção EBNF pode usar não somente ‘|’ mas também ‘*’ além de ‘(‘ e ‘)’. Diferentemente de uma RE ordinária , o lado direito pode conter símbolos não-terminais como também símbolos terminais. Logo , podemos escrever regras de produção recursivas, e uma EBNF é capaz de gerar uma linguagem com self-embedding.

  13. Conceitos Básicos Exemplo de gramática expressa em EBNF: Expression ::= primary-Expression (Operator primary-Expression)* primary-Expression ::= Identifier | (Expression) Identifier ::= a | b | c | d | e Operator ::= + | - | * | / Esta gramática gera expressões como: a + b a – b – c a + (b * c) a * (b + c) / d a – (b – (c – (d - e)))

  14. Conceitos Básicos Transformações de Gramática Fatoração à esquerda Ex;: XY | XZ equivale à X (Y | Z) single-Command ::= V-name := Expression | if Expression then single-Command | if Expression then single-Command else single-Command single-Command ::= V-name := Expression | if Expression then single-Command ( ε | else single-Command)

  15. Conceitos Básicos Transformações de Gramática Eliminação de recursão à esquerda N ::= X | NY, onde N é um símbolo não-terminal e X e Y são REs estendidas. Esta produção é recursiva à esquerda. N ::= X(Y)* Substituindo por uma regra EBNF equivalente

  16. Conceitos Básicos Transformações de Gramática Identifier ::= Letter | Identifier Letter | Identifier Digit Identifier ::= Letter | Identifier (Letter | Digit) Identifier ::= Letter (Letter | Digit)* Fatorando à esquerda Eliminando recursão à esquerda

  17. Conceitos Básicos A necessidade da eliminação de recursão à esquerda é melhor entendida depois que se vai usar a abordagem top-down;

  18. Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências

  19. Estratégias de Parsing Objetivo Reconhecimento de uma string de entrada (seqüência de tokens) e decisão se esta é ou não uma sentença de G Determinação de sua estrutura de frases (pode ser representada por uma árvore sintática) Gramática não ambígua: cada sentença tem exatamente uma syntax tree Top-Down Examina os símbolos terminais da esquerda para a direita Forma a ST (syntax tree) de cima para baixo Parsing ok: string de entrada totalmente conectada à ST L(eft-to-right) L(eft-most-derivation) => LL Bottom-Up Examina os símbolos terminais da esquerda para a direita Forma a ST (syntax tree) de baixo (nós terminais) para cima(nó raiz) Parsing ok: string de entrada reduzida a uma S-tree S(imple) L(eft-to-right) R(ight-most-derivation) => SLR L(eft-to-right) R(ight-most-derivation) => LR L(ook) A(head) L(eft-to-right) R(ight-most-derivation) => LALR

  20. Estratégia Bottom-Up Exemplo: Sentence ::= Subject Verb Object Subject ::= I | a Noun | the Noun Object ::= me | a Noun | the Noun Noun ::= cat | mat | rat Verb ::= like | is | see | sees the catseesarat . Sentence Aquielenãopoderiaterescolhido um Subject? Object Subject Noun Verb Noun

  21. Estratégia Top-Down Exemplo: Sentence ::= Subject Verb Object Subject ::= I | a Noun | the Noun Object ::= me | a Noun | the Noun Noun ::= cat | mat | rat Verb ::= like | is | see | sees the catseesarat . Sentence Object Subject Noun Verb Noun

  22. Estratégias de Parsing Qual estratégia de parsing devemos usar? Isso vai depender do tipo de gramática ! Sempre é necessário escolher qual regra de produção aplicar Isto é feito de acordo com o algoritmo de Parsing Recursive Descent é um algoritmo de parsing top-down Consiste de um grupo de métodos parseN, um para cada símbolo não-terminal N de G. Estes métodos cooperam para fazer o parse das sentenças completas parseSentence parseSubject parseVerb parseObject parseNoun parseNoun • the cat sees a rat .

  23. Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências

  24. Gramáticas LL(1) Deve-se expressar a gramática em EBNF, com uma regra de produção simples para cada símbolo não-terminal, e realizar as transformações de gramática necessárias, por exemplo, sempre eliminar recursão à esquerda e fatorizar à esquerda sempre que possível Gramáticas LL(1): Se a gramática contém X | Y, starters[[ X ]] e starters[[ Y ]] devem ser disjuntos Se a gramática contém X* , starters[[ X ]] deve ser disjunto do conjunto de tokens que podem seguir X* Na prática quase toda gramática de uma linguagem de programação pode ser transformada em LL(1), sem mudar a linguagem que a gramática gera Recursive-descent parsing é somente adequado para gramáticas LL(1) Em geral, o projetista da linguagem projeta a sua sintaxe para ser adequada à um parsing recursive-descent.

  25. Gramáticas não-LL(1) Exemplo de uma Gramáticas não LL(1): Program ::= single-Command Command ::= single-Command (; single-Command)* V-name ::= Identifier single-Command ::= V-name := Expression | Identifier ( Expression ) | if Expression then single-Command | if Expression then single-Command else single-Command … Expression ::= primary-Expression (Operator primary-Expression)* primary-Expression ::= Integer-Literal | Identifier …

  26. Gramáticas não-LL(1) Desenvolvimento do método parseSingleCommand: Uma simples fatoração à esquerda resolve o problema! private void parseSingleCommand(){ switch(currentToken.kind){ case Token.IDENTIFIER: { parseVname(); accept(Token.BECOMES); parseExpression(); } break; case Token.IDENTIFIER: { parseIdentifier (); accept(Token.LPAREN); parseExpression(); accept(Token.RPAREN); } break; } } … V-name ::= Identifier single-Command ::= V-name := Expression | Identifier ( Expression ) | … … single-Command ::= Identifier (:= Expression | ( Expression ) )

  27. Gramáticas não-LL(1) Exemplo de uma Gramáticas não LL(1): Aqui, starters[[ ;Declaration ]] = {;} e o conjunto de terminais que seguem (; Declaration)* neste contexto também é {;} Como não são disjuntos, então a gramática não é LL(1) … Block::= begin Declaration (; Declaration)* ; Command end Declaration ::= integer Identifier (, Identifier)* … Como resolver? private void parseBlock(){ accept(Token.BEGIN){ parseDeclaration(); while (currentToken.kind == Token.SEMICOLON) acceptIt(); parseDeclaration(); } accept(Token.SEMICOLON); parseCommand(); accept(Token.END); } … Block::= begin Declaration ; (Declaration;)* Command end …

  28. Recursive Descent Parser Recursive Descent Parser Algoritmo de parsing para gramáticas LL Visão geral Para cada produção N, crie um método parseN Crie uma classe parser com um atributo currentToken E os métodos parseN E os métodos auxiliares: accept e acceptIt E um método público parse que chama parseS O código de cada método parseN depende da produção N A árvore é dada implicitamente pela chamada dos métodos Pode ser criada explicitamente

  29. Recursive Descent Parser Programa ::= Expressao Expressao ::= ExpCondicionalOr ExpCondicionalOr ::= ExpCondicionalAnd ( ("or" ExpCondicionalAnd)* | ε)? ExpCondicionalAnd ::= ExpIgualdade ( ("and" ExpIgualdade)* | ε)? ExpIgualdade ::= ExpAritmetica ( ("==" ExpAritmetica)? | ε)? ExpAritmetica ::= ExpConcatenacao ( (("+" | "-") ExpConcatenacao)* | ε)? ExpConcatenacao ::= ExpUnaria ( ("++" ExpUnaria)* | ε)? ExpUnaria ::= "-" Expressao | "not" Expressao | "length" Expressao | ValorConcreto ValorConcreto ::= ValorInteiro | ValorBooleano | ValorString accept(int type) { if ( currentToken.getType() == type ) { currentToken = scanner.getNextToken(); } else { // ERRO } } Métodos auxiliares acceptIt() { currentToken = scanner.getNextToken(); }

  30. Recursive Descent Parser Programa ::= Expressao Expressao ::= ExpCondicionalOr ExpCondicionalOr ::= ExpCondicionalAnd ( ("or" ExpCondicionalAnd)* | ε)? ExpCondicionalAnd ::= ExpIgualdade ( ("and" ExpIgualdade)* | ε)? ExpIgualdade ::= ExpAritmetica ( ("==" ExpAritmetica)? | ε)? ExpAritmetica ::= ExpConcatenacao ( (("+" | "-") ExpConcatenacao)* | ε)? ExpConcatenacao ::= ExpUnaria ( ("++" ExpUnaria)* | ε)? ExpUnaria ::= "-" Expressao | "not" Expressao | "length" Expressao | ValorConcreto ValorConcreto ::= ValorInteiro | ValorBooleano | ValorString parse () { parsePrograma(); if ( currentToken.getType() != Token.EOT ) { // ERRO } } parseExpCondicionalOr() { parseExpCondicionalAnd(); while ( currentToken.getType() == Token.OR ) { acceptIt(); parseExpCondicionalAnd(); } } parseExpIgualdade() { parseExpAritmetica(); if ( currentToken.getType() == Token.EQUAL ) { acceptIt(); parseExpAritmetica(); } } parsePrograma() parseExpressao(); } parseExpressao() { parseExpCondicionalOr(); }

  31. Recursive Descent Parser Programa ::= Expressao Expressao ::= ExpCondicionalOr ExpCondicionalOr ::= ExpCondicionalAnd ( ("or" ExpCondicionalAnd)* | ε)? ExpCondicionalAnd ::= ExpIgualdade ( ("and" ExpIgualdade)* | ε)? ExpIgualdade ::= ExpAritmetica ( ("==" ExpAritmetica)? | ε)? ExpAritmetica ::= ExpConcatenacao ( (("+" | "-") ExpConcatenacao)* | ε)? ExpConcatenacao ::= ExpUnaria ( ("++" ExpUnaria)* | ε)? ExpUnaria ::= "-" Expressao | "not" Expressao | "length" Expressao | ValorConcreto ValorConcreto ::= ValorInteiro | ValorBooleano | ValorString parseExpUnaria() { if ( currentToken.getType() == Token.MINUS ) { acceptIt(); parseExpressao(); } else if ( currentToken.getType() == Token.NOT ) { acceptIt(); parseExpressao(); } else if ( currentToken.getType() == Token.LENGTH ) { acceptIt(); parseExpressao(); } else { parseValorConcreto(); } } parseValorConcreto() { if ( currentToken.getType() == Token.INT ) { acceptIt(); } else if ( currentToken.getType() == Token.BOOLEAN ) { acceptIt(); } else { accept(Token.STRING); } }

  32. Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências

  33. Referências WATT, D.; BROWN, D.Programming Language Processors in Java. Capítulo 4 Foco maior na abordagem LL AHO, A.; LAM, M.; SETHI, R.; ULLMAN, J.Compilers: Principles, Techniques & Tools. Capítulo 4 Foco maior na abordagem LR

  34. CIn / UFPE Parsing Tarciana Dias / Gustavo Carvalho tds@cin.ufpe.br / ghpc@cin.ufpe.br Março 2012

More Related