Parece que o GCC e o LLVM-Clang estão usando analisadores descendentes recursivos escritos à mão , e não gerados por máquina, baseados em Bison-Flex, análise de baixo para cima.
Alguém aqui poderia confirmar se este é o caso? E em caso afirmativo, por que os principais frameworks de compiladores usam analisadores escritos à mão?
Atualização : blog interessante sobre este tópico aqui
Respostas:
Sim:
O GCC usou um analisador yacc (bison) uma vez, mas foi substituído por um analisador descendente recursivo escrito à mão em algum ponto da série 3.x: consulte http://gcc.gnu.org/wiki/New_C_Parser para links para envios de patches relevantes.
O Clang também usa um analisador descendente recursivo escrito à mão: consulte a seção "Um único analisador unificado para C, Objective C, C ++ e Objective C ++" próximo ao final de http://clang.llvm.org/features.html .
fonte
foo * bar;
poderia analisar como uma expressão de multiplicação (com o resultado não usado) ou como uma declaração de uma variávelbar
com o tipo ponteiro-para-foo
. Qual é o correto depende se umtypedef
forfoo
está no escopo no momento, o que não é algo que pode ser determinado com qualquer olhar à frente. Mas isso significa apenas que o analisador descendente recursivo precisa de algum maquinário extra feio adicionado para lidar com isso.Existe um teorema popular que diz que C é difícil de analisar e C ++ essencialmente impossível.
Não é verdade.
O que é verdade é que C e C ++ são muito difíceis de analisar usando analisadores LALR (1) sem hackear o mecanismo de análise e confundir os dados da tabela de símbolos. Na verdade, o GCC costumava analisá-los, usando YACC e hackery adicional como esse, e sim, era feio. Agora o GCC usa analisadores escritos à mão, mas ainda com o hackery da tabela de símbolos. O pessoal do Clang nunca tentou usar geradores de analisador automatizado; AFAIK, o analisador Clang sempre foi descendente recursivo codificado manualmente.
O que é verdade, é que C e C ++ são relativamente fáceis de analisar com analisadores mais fortes gerados automaticamente, por exemplo, analisadores GLR , e você não precisa de nenhum hacks. O analisador Elsa C ++ é um exemplo disso. Nosso front-end C ++ é outro (assim como todos os nossos front-ends de "compilador", GLR é uma tecnologia de análise bastante maravilhosa).
Nosso front-end C ++ não é tão rápido quanto o do GCC e certamente mais lento que o Elsa; colocamos pouca energia em ajustá-lo cuidadosamente porque temos outros problemas mais urgentes (embora ele tenha sido usado em milhões de linhas de código C ++). Elsa é provavelmente mais lento que o GCC simplesmente porque é mais geral. Dadas as velocidades do processador hoje em dia, essas diferenças podem não importar muito na prática.
Mas os "compiladores reais" que são amplamente distribuídos hoje têm suas raízes em compiladores de 10 ou 20 anos atrás ou mais. As ineficiências importavam muito mais, e ninguém tinha ouvido falar dos analisadores GLR, então as pessoas faziam o que sabiam fazer. O Clang é certamente mais recente, mas os teoremas populares mantêm seu "poder de persuasão" por muito tempo.
Você não precisa mais fazer assim. Você pode usar o GLR e outros analisadores como front-ends, com uma melhoria na manutenção do compilador.
O que é verdade é que obter uma gramática que corresponda ao comportamento do compilador de sua vizinhança amigável é difícil. Embora praticamente todos os compiladores C ++ implementem (a maioria) do padrão original, eles também tendem a ter muitas extensões de canto escuro, por exemplo, especificações de DLL em compiladores MS, etc. Se você tiver um motor de análise forte, pode gastar seu tempo tentando obter a gramática final para corresponder à realidade, em vez de tentar dobrar sua gramática para corresponder às limitações de seu gerador de analisador.
EDITAR novembro de 2012: desde que escrevemos esta resposta, melhoramos nosso front-end C ++ para lidar com C ++ 11 completo, incluindo ANSI, GNU e dialetos variantes MS. Embora houvesse muitas coisas extras, não precisamos mudar nosso mecanismo de análise; acabamos de revisar as regras gramaticais. Tivemos que mudar a análise semântica; C ++ 11 é semanticamente muito complicado e este trabalho atrapalha o esforço para fazer o analisador funcionar.
EDITAR fevereiro de 2015: ... agora lida com C ++ 14 completo. (Consulte obter AST legível por humanos a partir do código c ++ para análises GLR de um simples trecho de código e a infame "análise mais irritante" do C ++).
EDITAR abril de 2017: agora lida com (rascunho) C ++ 17.
fonte
foo * bar;
ambigüidade?O analisador de Clang é um analisador descendente recursivo escrito à mão, assim como vários outros front-ends comerciais e de código aberto C e C ++.
O Clang usa um analisador descendente recursivo por vários motivos:
No geral, para um compilador C ++, simplesmente não importa muito: a parte de análise de C ++ não é trivial, mas ainda é uma das partes mais fáceis, então vale a pena mantê-la simples. A análise semântica - particularmente pesquisa de nome, inicialização, resolução de sobrecarga e instanciação de modelo - é muito mais complicada do que a análise. Se você quiser uma prova, verifique a distribuição de código e commits no componente "Sema" do Clang (para análise semântica) versus seu componente "Parse" (para análise).
fonte
O analisador do gcc é escrito à mão. . Eu suspeito o mesmo para clang. Provavelmente por alguns motivos:
Este provavelmente não é um caso de síndrome do "não foi inventado aqui", mas mais do tipo "não havia nada otimizado especificamente para o que precisávamos, então escrevemos o nosso próprio".
fonte
Respostas estranhas aí!
Gramáticas C / C ++ não são independentes de contexto. Eles são sensíveis ao contexto por causa da barra Foo *; ambiguidade. Temos que construir uma lista de typedefs para saber se Foo é um tipo ou não.
Ira Baxter: Não vejo sentido em sua coisa de GLR. Por que construir uma árvore de análise que contém ambigüidades. Analisar significa resolver ambigüidades, construir a árvore sintática. Você resolve essas ambigüidades em uma segunda passagem, então isso não é menos feio. Pra mim é bem mais feio ...
Yacc é um gerador de analisador sintático LR (1) (ou LALR (1)), mas pode ser facilmente modificado para ser sensível ao contexto. E não há nada de feio nisso. Yacc / Bison foi criado para ajudar na análise da linguagem C, então provavelmente não é a ferramenta mais feia para gerar um analisador C ...
Até o GCC 3.x, o analisador C é gerado pelo yacc / bison, com a tabela typedefs construída durante a análise. Com a construção de tabelas typedefs "em análise", a gramática C torna-se localmente livre de contexto e, além disso, "localmente LR (1)".
Agora, no Gcc 4.x, é um analisador descendente recursivo. É exatamente o mesmo analisador do Gcc 3.x, ainda é LR (1) e tem as mesmas regras gramaticais. A diferença é que o analisador yacc foi reescrito à mão, o deslocamento / redução agora estão ocultos na pilha de chamadas e não há "state454: if (nextsym == '(') goto state398" como em gcc 3.x yacc's analisador, por isso é mais fácil corrigir, lidar com erros e imprimir mensagens mais agradáveis, e para realizar algumas das próximas etapas de compilação durante a análise. Ao preço de muito menos código "fácil de ler" para um noob gcc.
Por que eles mudaram de yacc para descida recursiva? Porque é muito necessário evitar o yacc para analisar C ++, e porque o GCC sonha em ser um compilador multilíngue, ou seja, compartilhar o máximo de código entre as diferentes linguagens que ele pode compilar. É por isso que o C ++ e o analisador C são escritos da mesma maneira.
C ++ é mais difícil de analisar do que C porque não é LR (1) "localmente" como C, nem mesmo LR (k). Veja
func<4 > 2>
qual é uma função de modelo instanciada com 4> 2, ou sejafunc<4 > 2>
, deve ser lida comofunc<1>
. Definitivamente, não é LR (1). Agora considerefunc<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>
,. É aqui que uma descida recursiva pode facilmente resolver a ambiguidade, ao preço de mais algumas chamadas de função (parse_template_parameter é a função de analisador ambígua. Se parse_template_parameter (17tokens) falhou, tente novamente parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... até funciona).Não sei por que não seria possível adicionar em sub gramáticas recursivas yacc / bison, talvez esta seja a próxima etapa no desenvolvimento do analisador gcc / GNU?
fonte
Bison, em particular, não acho que possa lidar com a gramática sem analisar algumas coisas de forma ambígua e fazer uma segunda passagem depois.
Eu sei que o Happy de Haskell permite analisadores monádicos (ou seja, dependentes de estado) que podem resolver o problema específico com a sintaxe C, mas não conheço nenhum gerador de analisador C que permita uma mônada de estado fornecida pelo usuário.
Em teoria, a recuperação de erros seria um ponto a favor de um analisador manuscrito, mas minha experiência com o GCC / Clang mostra que as mensagens de erro não são particularmente boas.
Quanto ao desempenho - algumas das afirmações parecem infundadas. Gerar uma grande máquina de estado usando um gerador de analisador deve resultar em algo que é
O(n)
e eu duvido que a análise seja o gargalo de muitas ferramentas.fonte