340 likes | 502 Views
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).
E N D
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) 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
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
Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências
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
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])*
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
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
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
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.
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.
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)))
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)
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
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
Conceitos Básicos A necessidade da eliminação de recursão à esquerda é melhor entendida depois que se vai usar a abordagem top-down;
Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências
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
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
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
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 .
Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências
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.
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 …
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 ) )
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 …
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
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(); }
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(); }
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); } }
Roteiro Processo de Compilação Conceitos Básicos Estratégias de Parsing Gramáticas LL / Recursive Descent Algorithm Referências
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
CIn / UFPE Parsing Tarciana Dias / Gustavo Carvalho tds@cin.ufpe.br / ghpc@cin.ufpe.br Março 2012