O que a análise sem scanner tem a ver com o "Problema de Dangling Else"?


13

Não compreendo esta frase do artigo da Wikipedia sobre o problema de Dangling Else :

[O problema Dangling Else] é um problema que geralmente surge na construção do compilador, especialmente na análise sem scanner.

Alguém pode me explicar como as técnicas de análise sem scanner podem exacerbar esse problema? Parece-me que o problema está na gramática - já que é ambígua - e não na escolha da técnica de análise. o que estou perdendo?


2
A única coisa em que consigo pensar é que um analisador sem scanner precisa de uma gramática mais complexa, dificultando o fornecimento de heurísticas para resolver a ambiguidade.
Giorgio

3
@ Robert Harvey: O ponto é que essa suposição deve ser refletida pela árvore de sintaxe. Se uma gramática permite derivar duas árvores de sintaxe diferentes para a sequência if a then if b then s1 else s2, a gramática é ambígua.
Giorgio

1
O @RobertHarvey de uma maneira comum de definir idiomas é usar uma gramática livre de contexto, além de várias regras que desambiguam a gramática, se necessário.

2
Nem todos os analisadores criados sem scanner são iguais. Por exemplo, PEG ou GLR, um comportamento pendente é sempre previsível.
SK-logic

1
[O problema de Dangling Else] não tem nada a ver com análise sem scanner. [O problema Dangling Else] está relacionado às operações de redução de turno dos analisadores LR (bottom up). AFAIK
ddur

Respostas:


6

Meu melhor palpite é que a frase no artigo da Wikipedia resulta de um mal-entendido do trabalho de E. Visser.

Gramáticas para analisadores sem scanner (isto é, gramáticas que descrevem um idioma como conjunto de seqüências de caracteres em vez de um conjunto de sequências de tokens com os tokens descritos separadamente como sequências de caracteres) tendem a ter muitas ambiguidades. Papel de E. Visser Os filtros de desambiguação para analisadores de LR generalizados sem scanner (*) propõem vários mecanismos para solucionar ambiguidades, um dos quais é útil para resolver o problema do outro pendente. Mas o artigo não afirma que a ambiguidade precisa denominada "problema do outro pendente" esteja relacionada a analisadores sem scanner (nem mesmo que o mecanismo seja especialmente útil para analisadores sem scanner).

O fato de propor um mecanismo para resolvê-lo não é uma declaração implícita, pois outro mecanismo de resolução de ambiguidade (prioridade e precedência do operador) também parece totalmente não relacionado à natureza sem scanner dos analisadores considerados (considere, por exemplo, que essas ambiguidades não podem ser presentes nas gramáticas regulares como resultado do aninhamento, enquanto os manipulados por uma regra de correspondência mais longa podem).


(*) Esse é provavelmente o artigo que serve de base ao artigo da Wikipedia sobre analisadores sem scanner, mesmo que eles façam referência a outro, também por E. Visser, Analisador de LR sem Scanner Generalizado .


13

Apenas para declarar o problema, o Dangling Else Problem é uma ambiguidade na especificação da sintaxe do código, onde pode não ser claro, nos casos de ifs e elses seguidos, o que mais pertence a qual if.

O exemplo mais simples e clássico:

if(conditionA)
if(conditionB)
   doFoo();
else
   doBar();

Não está claro para quem não conhece as especificidades da especificação de idioma de cor, que ifrecebe o else(e esse trecho de código específico é válido em meia dúzia de idiomas, mas pode ter um desempenho diferente em cada um).

A construção Dangling Else apresenta um problema em potencial para implementações de analisador sem scanner, porque a estratégia é reduzir o fluxo de arquivos um caractere de cada vez, até que o analisador veja que tem o suficiente para tokenizar (digerir no assembly ou no idioma intermediário que está compilando) . Isso permite que o analisador mantenha o estado mínimo; assim que achar que possui informações suficientes para gravar os tokens analisados ​​no arquivo, isso será feito. Esse é o objetivo final de um analisador sem scanner; compilação rápida, simples e leve.

Supondo que novas linhas e espaços em branco antes ou depois da pontuação não tenham sentido (como na maioria dos idiomas do estilo C), essa declaração apareceria para o compilador como:

if(conditionA)if(conditionB)doFoo();else doBar;

Perfeitamente analisável para um computador, então vamos ver. Eu recebo um personagem de cada vez até ter:

if(conditionA)

Ah, eu sei o que isso significa (em C #), significa " pushcondiçãoA na pilha de avaliação e, em seguida, chamo brfalsepara pular para a instrução após o próximo ponto e vírgula, se não for verdade". No momento, não vejo ponto e vírgula, portanto, por enquanto, definirei meu deslocamento de salto para o próximo espaço após esta instrução e aumentarei esse deslocamento à medida que insiro mais instruções até ver um ponto e vírgula. Continuando a analisar ...

if(conditionB)

OK, isso analisa um par semelhante de operações de IL e segue imediatamente após a instrução que acabei de analisar. Como não vejo ponto-e-vírgula, aumentarei o deslocamento da minha declaração anterior pelo comprimento dos meus dois comandos (um para o push e outro para o break) e continuarei procurando.

doFoo();

Ok, é fácil. Isso é " calldoFoo". E isso é um ponto e vírgula que eu vejo? Bem, isso é ótimo, esse é o fim da linha. Vou incrementar as compensações de pulo de meus dois blocos pelo comprimento desses dois comandos e esquecer que alguma vez me importei. OK, seguindo em frente ...

else

... Uh-oh. Isso não é tão simples quanto parecia. OK, esqueci o que estava fazendo, mas isso elsesignifica que há uma declaração de interrupção condicional em algum lugar que eu já vi, então deixe-me olhar para trás ... sim, aí está brfalse, logo após pressionar alguma "condiçãoB" em a pilha, o que quer que fosse. OK, agora eu preciso de um incondicional breakcomo a próxima declaração. A declaração que virá depois disso agora é definitivamente o objetivo da minha interrupção condicional, por isso vou me certificar de que ela esteja certa e aumentarei a interrupção incondicional que eu introduzi. Seguindo em frente ...

doBar();

Isso é fácil. " calldoBar". E há um ponto e vírgula, e eu nunca vi aparelho. Portanto, o incondicional breakdeve passar para a próxima declaração, seja ela qual for, e posso esquecer que alguma vez me importei.


Então, o que temos ... (nota: são 22h e não tenho vontade de converter deslocamentos de bits em hexadecimal ou preencher o shell IL completo de uma função com esses comandos, então isso é apenas pseudo-IL usando números de linha onde normalmente haveria desvios de bytes):

ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>

Bem, isso realmente é executado corretamente, SE a regra (como na maioria das linguagens de estilo C) é elsea mais próxima if. Recuado para seguir o aninhamento da execução, ele seria executado assim, onde, se conditionA for false, o restante do snippet inteiro será ignorado:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();

... mas o faz por acaso, porque a quebra associada à ifinstrução externa salta para a breakinstrução no final do interno if , o que leva o ponteiro da execução além da instrução inteira. É um salto extra desnecessário e, se este exemplo for mais complexo, poderá não funcionar mais se analisado e tokenizado dessa maneira.

Além disso, e se a especificação da linguagem disser que um dangling elsepertence ao primeiro ife se a condiçãoA for falsa, o doBar será executado, enquanto que se a condiçãoA for verdadeira, mas não a condiçãoB, nada acontecerá, como isso?

if(conditionA)
    if(conditionB)
       doFoo();
else
   doBar();

O analisador havia esquecido o primeiro ifque existia e, portanto, esse algoritmo simples de analisador não produzia o código correto, para não falar em código eficiente.

Agora, o analisador pode ser inteligente o suficiente para lembrar os se ife elses por mais tempo, mas se a especificação do idioma indicar um único elsedepois de dois ifs corresponder ao primeiro if, isso causará um problema com dois ifs com elses correspondentes :

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();
else
    doBaz();

O analisador verá o primeiro else, corresponderá ao primeiro if, depois verá o segundo e entrará em pânico no modo "que diabos eu estava fazendo de novo"? Nesse ponto, o analisador obteve bastante código em um estado mutável que preferiria já ter enviado para o fluxo de arquivos de saída.

Existem soluções para todos esses problemas e what-ifs. Porém, o código necessário para ser inteligente aumenta a complexidade do algoritmo do analisador ou a especificação de idioma que permite que o analisador seja burro aumenta a verbosidade do código-fonte do idioma, como exigir instruções finais como end ifparênteses indicando colchetes bloqueia se a ifinstrução tiver um else(ambos os quais são comumente vistos em outros estilos de idioma).

Este é apenas um exemplo simples de algumas ifdeclarações, e observe todas as decisões que o compilador teve que tomar e onde ele poderia facilmente ter estragado tudo. Este é o detalhe por trás dessa declaração inócua da Wikipedia em sua pergunta.


1
Interessante, mas estou longe de ter certeza de que foi esse o objetivo do artigo da Wikipedia. Ele faz referência (através da entrada sem scanner) a um relatório da Eelco Visser, cujo conteúdo à primeira vista não é compatível com a sua explicação.
APJRrammer

3
Obrigado pela resposta, mas realmente não aborda o OP. Não concordo com as suposições no post sobre qual é o objetivo de um analisador sem scanner e como ele é implementado. Existem várias maneiras de implementar analisadores sem scanner e esta postagem parece lidar apenas com um subconjunto limitado.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.