A tradução para o código C é um hábito muito bem estabelecido. O C original com classes (e as implementações iniciais do C ++, então chamadas Cfront ) fizeram isso com êxito. Várias implementações do Lisp ou Scheme estão fazendo isso, por exemplo, Chicken Scheme , Scheme48 , Bigloo . Algumas pessoas traduzido Prolog de C . E o mesmo aconteceu com algumas versões do Mozart (e houve tentativas de compilar o código de código Ocaml para C ). O sistema CAIA de inteligência artificial da J.Pitrat também é inicializado e gera todo o seu código C. Vala também traduz para C, para código relacionado ao GTK. O livro de Queinnec, Lisp In Small Pieces tem algum capítulo sobre tradução para C.
Um dos problemas ao traduzir para C são as chamadas recursivas de cauda . O padrão C não garante que um compilador C os traduza adequadamente (para um "salto com argumentos", ou seja, sem comer pilha de chamadas), mesmo que em alguns casos, versões recentes do GCC (ou do Clang / LLVM) façam essa otimização .
Outra questão é a coleta de lixo . Várias implementações apenas usam o coletor de lixo conservador Boehm (que é compatível com C ...). Se você quisesse coletar o código de coleta de lixo (como várias implementações do Lisp, por exemplo, SBCL), isso pode ser um pesadelo (você gostaria dlclose
no Posix).
Outra questão é lidar com continuações de primeira classe e call / cc . Mas truques inteligentes são possíveis (veja o esquema de galinhas). Acessar a pilha de chamadas pode exigir muitos truques (mas consulte o GNU backtrace , etc ....). A persistência ortogonal de continuações (ou seja, de pilhas ou fios) seria difícil em C.
Manipulação de exceção geralmente é uma questão de emitir chamadas inteligentes para longjmp etc ...
Você pode gerar (no seu código C emitido) #line
diretivas apropriadas . Isso é chato e exige muito trabalho (por exemplo, você deve produzir gdb
código mais facilmente debocável).
Meu idioma específico do domínio lispy MELT (para personalizar ou estender o GCC ) é traduzido para C (na verdade, para C ++ ruim agora). Ele possui seu próprio coletor de lixo para cópia geracional. (Você pode estar interessado por Qish ou Ravenbrook MPS ). Na verdade, o GC geracional é mais fácil no código C gerado pela máquina do que no código C escrito à mão (porque você personalizará o seu gerador de código C para o seu equipamento de barreira contra gravação e GC).
Não conheço nenhuma implementação de linguagem traduzida para código C ++ genuíno , ou seja, usando alguma técnica de "coleta de lixo em tempo de compilação" para emitir código C ++ usando muitos modelos de STL e respeitando o idioma RAII . (por favor, diga se você conhece um).
O que é engraçado hoje é que (nos desktops atuais do Linux) os compiladores C podem ser rápidos o suficiente para implementar um loop interativo de leitura-avaliação-impressão traduzido para C: você emitirá código C (algumas centenas de linhas) a cada usuário interação, você fork
a compilará em um objeto compartilhado, o que você faria então dlopen
. (O MELT está fazendo isso tudo pronto, e geralmente é rápido o suficiente). Tudo isso pode levar alguns décimos de segundo e ser aceitável pelos usuários finais.
Quando possível, eu recomendaria a tradução para C, não para C ++, principalmente porque a compilação em C ++ é lenta.
Se você estiver implementando sua linguagem, também poderá considerar (em vez de emitir código C) algumas bibliotecas JIT como libjit , GNU lightning , asmjit ou mesmo LLVM ou GCCJIT . Se você deseja traduzir para C, às vezes pode usar tinycc : ele compila muito rapidamente o código C gerado (mesmo na memória) para diminuir o código da máquina. Mas, em geral, você deseja aproveitar as otimizações feitas por um compilador C real como o GCC
Se você traduzir para C seu idioma, certifique-se de criar o AST inteiro do código C gerado na memória primeiro (isso também facilita a geração de todas as declarações e de todas as definições e códigos de função). Você seria capaz de fazer algumas otimizações / normalizações dessa maneira. Além disso, você pode estar interessado em várias extensões do GCC (por exemplo, gotos computados). Você provavelmente desejará evitar a geração de grandes funções C - por exemplo, centenas de milhares de linhas de C geradas - (é melhor dividi-las em partes menores), pois a otimização de compiladores C é muito infeliz com funções C muito grandes (na prática, e experimentalmente,gcc -O
tempo de compilação de funções grandes é proporcional ao quadrado do tamanho do código da função). Portanto, limite o tamanho das funções C geradas a alguns milhares de linhas cada.
Observe que os compiladores Clang (através de LLVM ) e GCC (através de libgccjit ) oferecem alguma maneira de emitir algumas representações internas adequadas para esses compiladores, mas fazer isso pode (ou não) ser mais difícil do que emitir código C (ou C ++), e é específico para cada compilador.
Se você estiver projetando um idioma para ser traduzido para C, provavelmente precisará de vários truques (ou construções) para gerar uma mistura de C com seu idioma. Meu artigo sobre DSL2011 MELT: um idioma específico do domínio traduzido incorporado no compilador GCC deve fornecer dicas úteis.